Altair networking (#2300)

## Issue Addressed

Resolves #2278 

## Proposed Changes

Implements the networking components for the Altair hard fork https://github.com/ethereum/eth2.0-specs/blob/dev/specs/altair/p2p-interface.md

## Additional Info

This PR acts as the base branch for networking changes and tracks https://github.com/sigp/lighthouse/pull/2279 . Changes to gossip, rpc and discovery can be separate PRs to be merged here for ease of review.

Co-authored-by: realbigsean <seananderson33@gmail.com>
This commit is contained in:
Pawan Dhananjay
2021-08-04 01:44:57 +00:00
parent 6a620a31da
commit e8c0d1f19b
51 changed files with 4038 additions and 1354 deletions

View File

@@ -1,10 +1,12 @@
//! Implementation of Lighthouse's peer management system.
pub use self::peerdb::*;
use crate::discovery::TARGET_SUBNET_PEERS;
use crate::rpc::{GoodbyeReason, MetaData, Protocol, RPCError, RPCResponseErrorCode};
use crate::types::SyncState;
use crate::{error, metrics, Gossipsub};
use crate::{NetworkConfig, NetworkGlobals, PeerId};
use crate::{Subnet, SubnetDiscovery};
use discv5::Enr;
use futures::prelude::*;
use futures::Stream;
@@ -19,7 +21,7 @@ use std::{
task::{Context, Poll},
time::{Duration, Instant},
};
use types::{EthSpec, SubnetId};
use types::{EthSpec, SyncSubnetId};
pub use libp2p::core::{identity::Keypair, Multiaddr};
@@ -34,7 +36,7 @@ pub use peer_info::{ConnectionDirection, PeerConnectionStatus, PeerConnectionSta
pub use peer_sync_status::{PeerSyncStatus, SyncInfo};
use score::{PeerAction, ReportSource, ScoreState};
use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::{hash_map::Entry, HashMap};
use std::net::IpAddr;
/// The time in seconds between re-status's peers.
@@ -78,6 +80,11 @@ pub struct PeerManager<TSpec: EthSpec> {
target_peers: usize,
/// The maximum number of peers we allow (exceptions for subnet peers)
max_peers: usize,
/// A collection of sync committee subnets that we need to stay subscribed to.
/// Sync committee subnets are longer term (256 epochs). Hence, we need to re-run
/// discovery queries for subnet peers if we disconnect from existing sync
/// committee subnet peers.
sync_committee_subnets: HashMap<SyncSubnetId, Instant>,
/// The heartbeat interval to perform routine maintenance.
heartbeat: tokio::time::Interval,
/// Keeps track of whether the discovery service is enabled or not.
@@ -108,6 +115,8 @@ pub enum PeerManagerEvent {
UnBanned(PeerId, Vec<IpAddr>),
/// Request the behaviour to discover more peers.
DiscoverPeers,
/// Request the behaviour to discover peers on subnets.
DiscoverSubnetPeers(Vec<SubnetDiscovery>),
}
impl<TSpec: EthSpec> PeerManager<TSpec> {
@@ -127,6 +136,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
outbound_ping_peers: HashSetDelay::new(Duration::from_secs(PING_INTERVAL_OUTBOUND)),
status_peers: HashSetDelay::new(Duration::from_secs(STATUS_INTERVAL)),
target_peers: config.target_peers,
sync_committee_subnets: Default::default(),
max_peers: (config.target_peers as f32 * (1.0 + PEER_EXCESS_FACTOR)).ceil() as usize,
heartbeat,
discovery_enabled: !config.disable_discovery,
@@ -264,16 +274,16 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
}
/// Adds a gossipsub subscription to a peer in the peerdb.
pub fn add_subscription(&self, peer_id: &PeerId, subnet_id: SubnetId) {
pub fn add_subscription(&self, peer_id: &PeerId, subnet: Subnet) {
if let Some(info) = self.network_globals.peers.write().peer_info_mut(peer_id) {
info.subnets.insert(subnet_id);
info.subnets.insert(subnet);
}
}
/// Removes a gossipsub subscription to a peer in the peerdb.
pub fn remove_subscription(&self, peer_id: &PeerId, subnet_id: SubnetId) {
pub fn remove_subscription(&self, peer_id: &PeerId, subnet: Subnet) {
if let Some(info) = self.network_globals.peers.write().peer_info_mut(peer_id) {
info.subnets.remove(&subnet_id);
info.subnets.remove(&subnet);
}
}
@@ -284,6 +294,21 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
}
}
/// Insert the sync subnet into list of long lived sync committee subnets that we need to
/// maintain adequate number of peers for.
pub fn add_sync_subnet(&mut self, subnet_id: SyncSubnetId, min_ttl: Instant) {
match self.sync_committee_subnets.entry(subnet_id) {
Entry::Vacant(_) => {
self.sync_committee_subnets.insert(subnet_id, min_ttl);
}
Entry::Occupied(old) => {
if *old.get() < min_ttl {
self.sync_committee_subnets.insert(subnet_id, min_ttl);
}
}
}
}
/* Notifications from the Swarm */
// A peer is being dialed.
@@ -599,9 +624,9 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
// if the sequence number is unknown send an update the meta data of the peer.
if let Some(meta_data) = &peer_info.meta_data {
if meta_data.seq_number < seq {
if *meta_data.seq_number() < seq {
debug!(self.log, "Requesting new metadata from peer";
"peer_id" => %peer_id, "known_seq_no" => meta_data.seq_number, "ping_seq_no" => seq);
"peer_id" => %peer_id, "known_seq_no" => meta_data.seq_number(), "ping_seq_no" => seq);
self.events.push(PeerManagerEvent::MetaData(*peer_id));
}
} else {
@@ -623,9 +648,9 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
// if the sequence number is unknown send update the meta data of the peer.
if let Some(meta_data) = &peer_info.meta_data {
if meta_data.seq_number < seq {
if *meta_data.seq_number() < seq {
debug!(self.log, "Requesting new metadata from peer";
"peer_id" => %peer_id, "known_seq_no" => meta_data.seq_number, "pong_seq_no" => seq);
"peer_id" => %peer_id, "known_seq_no" => meta_data.seq_number(), "pong_seq_no" => seq);
self.events.push(PeerManagerEvent::MetaData(*peer_id));
}
} else {
@@ -643,19 +668,19 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
pub fn meta_data_response(&mut self, peer_id: &PeerId, meta_data: MetaData<TSpec>) {
if let Some(peer_info) = self.network_globals.peers.write().peer_info_mut(peer_id) {
if let Some(known_meta_data) = &peer_info.meta_data {
if known_meta_data.seq_number < meta_data.seq_number {
if *known_meta_data.seq_number() < *meta_data.seq_number() {
debug!(self.log, "Updating peer's metadata";
"peer_id" => %peer_id, "known_seq_no" => known_meta_data.seq_number, "new_seq_no" => meta_data.seq_number);
"peer_id" => %peer_id, "known_seq_no" => known_meta_data.seq_number(), "new_seq_no" => meta_data.seq_number());
} else {
debug!(self.log, "Received old metadata";
"peer_id" => %peer_id, "known_seq_no" => known_meta_data.seq_number, "new_seq_no" => meta_data.seq_number);
"peer_id" => %peer_id, "known_seq_no" => known_meta_data.seq_number(), "new_seq_no" => meta_data.seq_number());
// Updating metadata even in this case to prevent storing
// incorrect `metadata.attnets` for a peer
// incorrect `attnets/syncnets` for a peer
}
} else {
// we have no meta-data for this peer, update
debug!(self.log, "Obtained peer's metadata";
"peer_id" => %peer_id, "new_seq_no" => meta_data.seq_number);
"peer_id" => %peer_id, "new_seq_no" => meta_data.seq_number());
}
peer_info.meta_data = Some(meta_data);
} else {
@@ -965,6 +990,46 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
Ok(())
}
/// Run discovery query for additional sync committee peers if we fall below `TARGET_PEERS`.
fn maintain_sync_committee_peers(&mut self) {
// Remove expired entries
self.sync_committee_subnets
.retain(|_, v| *v > Instant::now());
let subnets_to_discover: Vec<SubnetDiscovery> = self
.sync_committee_subnets
.iter()
.filter_map(|(k, v)| {
if self
.network_globals
.peers
.read()
.good_peers_on_subnet(Subnet::SyncCommittee(*k))
.count()
< TARGET_SUBNET_PEERS
{
Some(SubnetDiscovery {
subnet: Subnet::SyncCommittee(*k),
min_ttl: Some(*v),
})
} else {
None
}
})
.collect();
// request the subnet query from discovery
if !subnets_to_discover.is_empty() {
debug!(
self.log,
"Making subnet queries for maintaining sync committee peers";
"subnets" => ?subnets_to_discover.iter().map(|s| s.subnet).collect::<Vec<_>>()
);
self.events
.push(PeerManagerEvent::DiscoverSubnetPeers(subnets_to_discover));
}
}
/// The Peer manager's heartbeat maintains the peer count and maintains peer reputations.
///
/// It will request discovery queries if the peer count has not reached the desired number of
@@ -989,6 +1054,9 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
// Updates peer's scores.
self.update_peer_scores();
// Maintain minimum count for sync committee peers.
self.maintain_sync_committee_peers();
// Keep a list of peers we are disconnecting
let mut disconnecting_peers = Vec::new();
@@ -1115,7 +1183,7 @@ mod tests {
use super::*;
use crate::discovery::enr::build_enr;
use crate::discovery::enr_ext::CombinedKeyExt;
use crate::rpc::methods::MetaData;
use crate::rpc::methods::{MetaData, MetaDataV2};
use crate::Enr;
use discv5::enr::CombinedKey;
use slog::{o, Drain};
@@ -1156,10 +1224,11 @@ mod tests {
enr,
9000,
9000,
MetaData {
MetaData::V2(MetaDataV2 {
seq_number: 0,
attnets: Default::default(),
},
syncnets: Default::default(),
}),
vec![],
&log,
);

View File

@@ -1,8 +1,8 @@
use super::client::Client;
use super::score::{PeerAction, Score, ScoreState};
use super::PeerSyncStatus;
use crate::rpc::MetaData;
use crate::Multiaddr;
use crate::{rpc::MetaData, types::Subnet};
use discv5::Enr;
use serde::{
ser::{SerializeStruct, Serializer},
@@ -12,7 +12,7 @@ use std::collections::HashSet;
use std::net::{IpAddr, SocketAddr};
use std::time::Instant;
use strum::AsRefStr;
use types::{EthSpec, SubnetId};
use types::EthSpec;
use PeerConnectionStatus::*;
/// Information about a given connected peer.
@@ -40,7 +40,7 @@ pub struct PeerInfo<T: EthSpec> {
/// connection.
pub meta_data: Option<MetaData<T>>,
/// Subnets the peer is connected to.
pub subnets: HashSet<SubnetId>,
pub subnets: HashSet<Subnet>,
/// The time we would like to retain this peer. After this time, the peer is no longer
/// necessary.
#[serde(skip)]
@@ -84,17 +84,26 @@ impl<T: EthSpec> PeerInfo<T> {
}
}
/// Returns if the peer is subscribed to a given `SubnetId` from the metadata attnets field.
pub fn on_subnet_metadata(&self, subnet_id: SubnetId) -> bool {
/// Returns if the peer is subscribed to a given `Subnet` from the metadata attnets/syncnets field.
pub fn on_subnet_metadata(&self, subnet: &Subnet) -> bool {
if let Some(meta_data) = &self.meta_data {
return meta_data.attnets.get(*subnet_id as usize).unwrap_or(false);
match subnet {
Subnet::Attestation(id) => {
return meta_data.attnets().get(**id as usize).unwrap_or(false)
}
Subnet::SyncCommittee(id) => {
return meta_data
.syncnets()
.map_or(false, |s| s.get(**id as usize).unwrap_or(false))
}
}
}
false
}
/// Returns if the peer is subscribed to a given `SubnetId` from the gossipsub subscriptions.
pub fn on_subnet_gossipsub(&self, subnet_id: SubnetId) -> bool {
self.subnets.contains(&subnet_id)
/// Returns if the peer is subscribed to a given `Subnet` from the gossipsub subscriptions.
pub fn on_subnet_gossipsub(&self, subnet: &Subnet) -> bool {
self.subnets.contains(subnet)
}
/// Returns the seen IP addresses of the peer.

View File

@@ -1,16 +1,19 @@
use super::peer_info::{ConnectionDirection, PeerConnectionStatus, PeerInfo};
use super::peer_sync_status::PeerSyncStatus;
use super::score::{Score, ScoreState};
use crate::multiaddr::{Multiaddr, Protocol};
use crate::rpc::methods::MetaData;
use crate::Enr;
use crate::PeerId;
use crate::{
multiaddr::{Multiaddr, Protocol},
types::Subnet,
};
use rand::seq::SliceRandom;
use slog::{crit, debug, error, trace, warn};
use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr};
use std::time::Instant;
use types::{EthSpec, SubnetId};
use types::EthSpec;
/// Max number of disconnected nodes to remember.
const MAX_DC_PEERS: usize = 500;
@@ -267,14 +270,14 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
}
/// Gives an iterator of all peers on a given subnet.
pub fn good_peers_on_subnet(&self, subnet_id: SubnetId) -> impl Iterator<Item = &PeerId> {
pub fn good_peers_on_subnet(&self, subnet: Subnet) -> impl Iterator<Item = &PeerId> {
self.peers
.iter()
.filter(move |(_, info)| {
// We check both the metadata and gossipsub data as we only want to count long-lived subscribed peers
info.is_connected()
&& info.on_subnet_metadata(subnet_id)
&& info.on_subnet_gossipsub(subnet_id)
&& info.on_subnet_metadata(&subnet)
&& info.on_subnet_gossipsub(&subnet)
&& info.is_good_gossipsub_peer()
})
.map(|(peer_id, _)| peer_id)
@@ -382,11 +385,11 @@ impl<TSpec: EthSpec> PeerDB<TSpec> {
/// Extends the ttl of all peers on the given subnet that have a shorter
/// min_ttl than what's given.
pub fn extend_peers_on_subnet(&mut self, subnet_id: SubnetId, min_ttl: Instant) {
pub fn extend_peers_on_subnet(&mut self, subnet: &Subnet, min_ttl: Instant) {
let log = &self.log;
self.peers.iter_mut()
.filter(move |(_, info)| {
info.is_connected() && info.on_subnet_metadata(subnet_id) && info.on_subnet_gossipsub(subnet_id)
info.is_connected() && info.on_subnet_metadata(subnet) && info.on_subnet_gossipsub(subnet)
})
.for_each(|(peer_id,info)| {
if info.min_ttl.is_none() || Some(min_ttl) > info.min_ttl {