mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-01 03:33:47 +00:00
Libp2p v0.48.0 upgrade (#3547)
## Issue Addressed Upgrades libp2p to v.0.47.0. This is the compilation of - [x] #3495 - [x] #3497 - [x] #3491 - [x] #3546 - [x] #3553 Co-authored-by: Age Manning <Age@AgeManning.com>
This commit is contained in:
@@ -232,7 +232,6 @@ impl CombinedKeyExt for CombinedKey {
|
||||
.expect("libp2p key must be valid");
|
||||
Ok(CombinedKey::from(ed_keypair))
|
||||
}
|
||||
_ => Err("ENR: Unsupported libp2p key type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,7 +265,6 @@ pub fn peer_id_to_node_id(peer_id: &PeerId) -> Result<discv5::enr::NodeId, Strin
|
||||
hasher.finalize(&mut output);
|
||||
Ok(discv5::enr::NodeId::parse(&output).expect("Must be correct length"))
|
||||
}
|
||||
_ => Err("Unsupported public key".into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ pub(crate) mod enr;
|
||||
pub mod enr_ext;
|
||||
|
||||
// Allow external use of the lighthouse ENR builder
|
||||
use crate::behaviour::TARGET_SUBNET_PEERS;
|
||||
use crate::metrics;
|
||||
use crate::service::TARGET_SUBNET_PEERS;
|
||||
use crate::{error, Enr, NetworkConfig, NetworkGlobals, Subnet, SubnetDiscovery};
|
||||
use discv5::{enr::NodeId, Discv5, Discv5Event};
|
||||
pub use enr::{
|
||||
@@ -21,6 +21,8 @@ pub use libp2p::core::identity::{Keypair, PublicKey};
|
||||
use enr::{ATTESTATION_BITFIELD_ENR_KEY, ETH2_ENR_KEY, SYNC_COMMITTEE_BITFIELD_ENR_KEY};
|
||||
use futures::prelude::*;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use libp2p::multiaddr::Protocol;
|
||||
use libp2p::swarm::AddressScore;
|
||||
pub use libp2p::{
|
||||
core::{connection::ConnectionId, ConnectedPoint, Multiaddr, PeerId},
|
||||
swarm::{
|
||||
@@ -67,13 +69,11 @@ pub const FIND_NODE_QUERY_CLOSEST_PEERS: usize = 16;
|
||||
/// The threshold for updating `min_ttl` on a connected peer.
|
||||
const DURATION_DIFFERENCE: Duration = Duration::from_millis(1);
|
||||
|
||||
/// The events emitted by polling discovery.
|
||||
pub enum DiscoveryEvent {
|
||||
/// A query has completed. This result contains a mapping of discovered peer IDs to the `min_ttl`
|
||||
/// of the peer if it is specified.
|
||||
QueryResult(HashMap<PeerId, Option<Instant>>),
|
||||
/// This indicates that our local UDP socketaddr has been updated and we should inform libp2p.
|
||||
SocketUpdated(SocketAddr),
|
||||
/// A query has completed. This result contains a mapping of discovered peer IDs to the `min_ttl`
|
||||
/// of the peer if it is specified.
|
||||
#[derive(Debug)]
|
||||
pub struct DiscoveredPeers {
|
||||
pub peers: HashMap<PeerId, Option<Instant>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
@@ -362,7 +362,7 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
}
|
||||
|
||||
/// Returns an iterator over all enr entries in the DHT.
|
||||
pub fn table_entries_enr(&mut self) -> Vec<Enr> {
|
||||
pub fn table_entries_enr(&self) -> Vec<Enr> {
|
||||
self.discv5.table_entries_enr()
|
||||
}
|
||||
|
||||
@@ -909,7 +909,7 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
impl<TSpec: EthSpec> NetworkBehaviour for Discovery<TSpec> {
|
||||
// Discovery is not a real NetworkBehaviour...
|
||||
type ConnectionHandler = libp2p::swarm::handler::DummyConnectionHandler;
|
||||
type OutEvent = DiscoveryEvent;
|
||||
type OutEvent = DiscoveredPeers;
|
||||
|
||||
fn new_handler(&mut self) -> Self::ConnectionHandler {
|
||||
libp2p::swarm::handler::DummyConnectionHandler::default()
|
||||
@@ -976,11 +976,9 @@ impl<TSpec: EthSpec> NetworkBehaviour for Discovery<TSpec> {
|
||||
self.process_queue();
|
||||
|
||||
// Drive the queries and return any results from completed queries
|
||||
if let Some(results) = self.poll_queries(cx) {
|
||||
if let Some(peers) = self.poll_queries(cx) {
|
||||
// return the result to the peer manager
|
||||
return Poll::Ready(NBAction::GenerateEvent(DiscoveryEvent::QueryResult(
|
||||
results,
|
||||
)));
|
||||
return Poll::Ready(NBAction::GenerateEvent(DiscoveredPeers { peers }));
|
||||
}
|
||||
|
||||
// Process the server event stream
|
||||
@@ -1019,8 +1017,8 @@ impl<TSpec: EthSpec> NetworkBehaviour for Discovery<TSpec> {
|
||||
}
|
||||
*/
|
||||
}
|
||||
Discv5Event::SocketUpdated(socket) => {
|
||||
info!(self.log, "Address updated"; "ip" => %socket.ip(), "udp_port" => %socket.port());
|
||||
Discv5Event::SocketUpdated(socket_addr) => {
|
||||
info!(self.log, "Address updated"; "ip" => %socket_addr.ip(), "udp_port" => %socket_addr.port());
|
||||
metrics::inc_counter(&metrics::ADDRESS_UPDATE_COUNT);
|
||||
metrics::check_nat();
|
||||
// Discv5 will have updated our local ENR. We save the updated version
|
||||
@@ -1029,9 +1027,16 @@ impl<TSpec: EthSpec> NetworkBehaviour for Discovery<TSpec> {
|
||||
enr::save_enr_to_disk(Path::new(&self.enr_dir), &enr, &self.log);
|
||||
// update network globals
|
||||
*self.network_globals.local_enr.write() = enr;
|
||||
return Poll::Ready(NBAction::GenerateEvent(
|
||||
DiscoveryEvent::SocketUpdated(socket),
|
||||
));
|
||||
// A new UDP socket has been detected.
|
||||
// Build a multiaddr to report to libp2p
|
||||
let mut address = Multiaddr::from(socket_addr.ip());
|
||||
// NOTE: This doesn't actually track the external TCP port. More sophisticated NAT handling
|
||||
// should handle this.
|
||||
address.push(Protocol::Tcp(self.network_globals.listen_port_tcp()));
|
||||
return Poll::Ready(NBAction::ReportObservedAddr {
|
||||
address,
|
||||
score: AddressScore::Finite(1),
|
||||
});
|
||||
}
|
||||
Discv5Event::EnrAdded { .. }
|
||||
| Discv5Event::TalkRequest(_)
|
||||
|
||||
@@ -5,15 +5,14 @@
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
pub mod behaviour;
|
||||
mod config;
|
||||
pub mod service;
|
||||
|
||||
#[allow(clippy::mutable_key_type)] // PeerId in hashmaps are no longer permitted by clippy
|
||||
pub mod discovery;
|
||||
pub mod metrics;
|
||||
pub mod peer_manager;
|
||||
pub mod rpc;
|
||||
mod service;
|
||||
pub mod types;
|
||||
|
||||
pub use config::gossip_max_size;
|
||||
@@ -69,7 +68,6 @@ pub use crate::types::{
|
||||
|
||||
pub use prometheus_client;
|
||||
|
||||
pub use behaviour::{BehaviourEvent, Gossipsub, PeerRequestId, Request, Response};
|
||||
pub use config::Config as NetworkConfig;
|
||||
pub use discovery::{CombinedKeyExt, EnrExt, Eth2Enr};
|
||||
pub use discv5;
|
||||
@@ -85,4 +83,7 @@ pub use peer_manager::{
|
||||
peerdb::PeerDB,
|
||||
ConnectionDirection, PeerConnectionStatus, PeerInfo, PeerManager, SyncInfo, SyncStatus,
|
||||
};
|
||||
pub use service::{load_private_key, Context, Libp2pEvent, Service, NETWORK_KEY_FILENAME};
|
||||
// pub use service::{load_private_key, Context, Libp2pEvent, Service, NETWORK_KEY_FILENAME};
|
||||
pub use service::api_types::{PeerRequestId, Request, Response};
|
||||
pub use service::utils::*;
|
||||
pub use service::{Gossipsub, NetworkEvent};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Implementation of Lighthouse's peer management system.
|
||||
|
||||
use crate::behaviour::TARGET_SUBNET_PEERS;
|
||||
use crate::rpc::{GoodbyeReason, MetaData, Protocol, RPCError, RPCResponseErrorCode};
|
||||
use crate::service::TARGET_SUBNET_PEERS;
|
||||
use crate::{error, metrics, Gossipsub};
|
||||
use crate::{NetworkGlobals, PeerId};
|
||||
use crate::{Subnet, SubnetDiscovery};
|
||||
@@ -12,6 +12,7 @@ use peerdb::{client::ClientKind, BanOperation, BanResult, ScoreUpdateResult};
|
||||
use rand::seq::SliceRandom;
|
||||
use slog::{debug, error, trace, warn};
|
||||
use smallvec::SmallVec;
|
||||
use std::collections::VecDeque;
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
@@ -71,6 +72,8 @@ pub struct PeerManager<TSpec: EthSpec> {
|
||||
status_peers: HashSetDelay<PeerId>,
|
||||
/// The target number of peers we would like to connect to.
|
||||
target_peers: usize,
|
||||
/// Peers queued to be dialed.
|
||||
peers_to_dial: VecDeque<(PeerId, Option<Enr>)>,
|
||||
/// 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
|
||||
@@ -115,7 +118,7 @@ pub enum PeerManagerEvent {
|
||||
|
||||
impl<TSpec: EthSpec> PeerManager<TSpec> {
|
||||
// NOTE: Must be run inside a tokio executor.
|
||||
pub async fn new(
|
||||
pub fn new(
|
||||
cfg: config::Config,
|
||||
network_globals: Arc<NetworkGlobals<TSpec>>,
|
||||
log: &slog::Logger,
|
||||
@@ -135,6 +138,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
|
||||
Ok(PeerManager {
|
||||
network_globals,
|
||||
events: SmallVec::new(),
|
||||
peers_to_dial: Default::default(),
|
||||
inbound_ping_peers: HashSetDelay::new(Duration::from_secs(ping_interval_inbound)),
|
||||
outbound_ping_peers: HashSetDelay::new(Duration::from_secs(ping_interval_outbound)),
|
||||
status_peers: HashSetDelay::new(Duration::from_secs(status_interval)),
|
||||
@@ -360,8 +364,8 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
|
||||
/* Notifications from the Swarm */
|
||||
|
||||
// A peer is being dialed.
|
||||
pub fn inject_dialing(&mut self, peer_id: &PeerId, enr: Option<Enr>) {
|
||||
self.inject_peer_connection(peer_id, ConnectingType::Dialing, enr);
|
||||
pub fn dial_peer(&mut self, peer_id: &PeerId, enr: Option<Enr>) {
|
||||
self.peers_to_dial.push_back((*peer_id, enr));
|
||||
}
|
||||
|
||||
/// Reports if a peer is banned or not.
|
||||
@@ -1247,9 +1251,7 @@ mod tests {
|
||||
};
|
||||
let log = build_log(slog::Level::Debug, false);
|
||||
let globals = NetworkGlobals::new_test_globals(&log);
|
||||
PeerManager::new(config, Arc::new(globals), &log)
|
||||
.await
|
||||
.unwrap()
|
||||
PeerManager::new(config, Arc::new(globals), &log).unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::task::{Context, Poll};
|
||||
use futures::StreamExt;
|
||||
use libp2p::core::connection::ConnectionId;
|
||||
use libp2p::core::ConnectedPoint;
|
||||
use libp2p::swarm::dial_opts::{DialOpts, PeerCondition};
|
||||
use libp2p::swarm::handler::DummyConnectionHandler;
|
||||
use libp2p::swarm::{
|
||||
ConnectionHandler, DialError, NetworkBehaviour, NetworkBehaviourAction, PollParameters,
|
||||
@@ -16,7 +17,7 @@ use crate::rpc::GoodbyeReason;
|
||||
use crate::types::SyncState;
|
||||
|
||||
use super::peerdb::BanResult;
|
||||
use super::{PeerManager, PeerManagerEvent, ReportSource};
|
||||
use super::{ConnectingType, PeerManager, PeerManagerEvent, ReportSource};
|
||||
|
||||
impl<TSpec: EthSpec> NetworkBehaviour for PeerManager<TSpec> {
|
||||
type ConnectionHandler = DummyConnectionHandler;
|
||||
@@ -99,6 +100,17 @@ impl<TSpec: EthSpec> NetworkBehaviour for PeerManager<TSpec> {
|
||||
self.events.shrink_to_fit();
|
||||
}
|
||||
|
||||
if let Some((peer_id, maybe_enr)) = self.peers_to_dial.pop_front() {
|
||||
self.inject_peer_connection(&peer_id, ConnectingType::Dialing, maybe_enr);
|
||||
let handler = self.new_handler();
|
||||
return Poll::Ready(NetworkBehaviourAction::Dial {
|
||||
opts: DialOpts::peer_id(peer_id)
|
||||
.condition(PeerCondition::Disconnected)
|
||||
.build(),
|
||||
handler,
|
||||
});
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! As the logic develops this documentation will advance.
|
||||
//!
|
||||
//! The scoring algorithms are currently experimental.
|
||||
use crate::behaviour::gossipsub_scoring_parameters::GREYLIST_THRESHOLD as GOSSIPSUB_GREYLIST_THRESHOLD;
|
||||
use crate::service::gossipsub_scoring_parameters::GREYLIST_THRESHOLD as GOSSIPSUB_GREYLIST_THRESHOLD;
|
||||
use serde::Serialize;
|
||||
use std::time::Instant;
|
||||
use strum::AsRefStr;
|
||||
|
||||
@@ -90,6 +90,7 @@ impl<T: EthSpec, Id: std::fmt::Debug> std::fmt::Display for RPCSend<Id, T> {
|
||||
}
|
||||
|
||||
/// Messages sent to the user from the RPC protocol.
|
||||
#[derive(Debug)]
|
||||
pub struct RPCMessage<Id, TSpec: EthSpec> {
|
||||
/// The peer that sent the message.
|
||||
pub peer_id: PeerId,
|
||||
|
||||
@@ -1,573 +0,0 @@
|
||||
use crate::behaviour::{
|
||||
save_metadata_to_disk, Behaviour, BehaviourEvent, PeerRequestId, Request, Response,
|
||||
};
|
||||
use crate::config::NetworkLoad;
|
||||
use crate::discovery::enr;
|
||||
use crate::multiaddr::Protocol;
|
||||
use crate::rpc::{GoodbyeReason, MetaData, MetaDataV1, MetaDataV2, RPCResponseErrorCode, ReqId};
|
||||
use crate::types::{error, EnrAttestationBitfield, EnrSyncCommitteeBitfield, GossipKind};
|
||||
use crate::EnrExt;
|
||||
use crate::{NetworkConfig, NetworkGlobals, PeerAction, ReportSource};
|
||||
use futures::prelude::*;
|
||||
use libp2p::core::{
|
||||
identity::Keypair, multiaddr::Multiaddr, muxing::StreamMuxerBox, transport::Boxed,
|
||||
};
|
||||
use libp2p::{
|
||||
bandwidth::{BandwidthLogging, BandwidthSinks},
|
||||
core, noise,
|
||||
swarm::{ConnectionLimits, SwarmBuilder, SwarmEvent},
|
||||
PeerId, Swarm, Transport,
|
||||
};
|
||||
use prometheus_client::registry::Registry;
|
||||
use slog::{crit, debug, info, o, trace, warn, Logger};
|
||||
use ssz::Decode;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use types::{ChainSpec, EnrForkId, EthSpec, ForkContext};
|
||||
|
||||
use crate::peer_manager::{MIN_OUTBOUND_ONLY_FACTOR, PEER_EXCESS_FACTOR, PRIORITY_PEER_EXCESS};
|
||||
|
||||
pub const NETWORK_KEY_FILENAME: &str = "key";
|
||||
/// The maximum simultaneous libp2p connections per peer.
|
||||
const MAX_CONNECTIONS_PER_PEER: u32 = 1;
|
||||
/// The filename to store our local metadata.
|
||||
pub const METADATA_FILENAME: &str = "metadata";
|
||||
|
||||
/// The types of events than can be obtained from polling the libp2p service.
|
||||
///
|
||||
/// This is a subset of the events that a libp2p swarm emits.
|
||||
#[derive(Debug)]
|
||||
pub enum Libp2pEvent<AppReqId: ReqId, TSpec: EthSpec> {
|
||||
/// A behaviour event
|
||||
Behaviour(BehaviourEvent<AppReqId, TSpec>),
|
||||
/// A new listening address has been established.
|
||||
NewListenAddr(Multiaddr),
|
||||
/// We reached zero listening addresses.
|
||||
ZeroListeners,
|
||||
}
|
||||
|
||||
/// The configuration and state of the libp2p components for the beacon node.
|
||||
pub struct Service<AppReqId: ReqId, TSpec: EthSpec> {
|
||||
/// The libp2p Swarm handler.
|
||||
pub swarm: Swarm<Behaviour<AppReqId, TSpec>>,
|
||||
/// The bandwidth logger for the underlying libp2p transport.
|
||||
pub bandwidth: Arc<BandwidthSinks>,
|
||||
/// This node's PeerId.
|
||||
pub local_peer_id: PeerId,
|
||||
/// The libp2p logger handle.
|
||||
pub log: Logger,
|
||||
}
|
||||
|
||||
pub struct Context<'a> {
|
||||
pub config: &'a NetworkConfig,
|
||||
pub enr_fork_id: EnrForkId,
|
||||
pub fork_context: Arc<ForkContext>,
|
||||
pub chain_spec: &'a ChainSpec,
|
||||
pub gossipsub_registry: Option<&'a mut Registry>,
|
||||
}
|
||||
|
||||
impl<AppReqId: ReqId, TSpec: EthSpec> Service<AppReqId, TSpec> {
|
||||
pub async fn new(
|
||||
executor: task_executor::TaskExecutor,
|
||||
ctx: Context<'_>,
|
||||
log: &Logger,
|
||||
) -> error::Result<(Arc<NetworkGlobals<TSpec>>, Self)> {
|
||||
let log = log.new(o!("service"=> "libp2p"));
|
||||
trace!(log, "Libp2p Service starting");
|
||||
|
||||
let config = ctx.config;
|
||||
// initialise the node's ID
|
||||
let local_keypair = load_private_key(config, &log);
|
||||
|
||||
// Create an ENR or load from disk if appropriate
|
||||
let enr =
|
||||
enr::build_or_load_enr::<TSpec>(local_keypair.clone(), config, &ctx.enr_fork_id, &log)?;
|
||||
|
||||
let local_peer_id = enr.peer_id();
|
||||
|
||||
// Construct the metadata
|
||||
let meta_data = load_or_build_metadata(&config.network_dir, &log);
|
||||
|
||||
// set up a collection of variables accessible outside of the network crate
|
||||
let network_globals = Arc::new(NetworkGlobals::new(
|
||||
enr.clone(),
|
||||
config.libp2p_port,
|
||||
config.discovery_port,
|
||||
meta_data,
|
||||
config
|
||||
.trusted_peers
|
||||
.iter()
|
||||
.map(|x| PeerId::from(x.clone()))
|
||||
.collect(),
|
||||
&log,
|
||||
));
|
||||
|
||||
info!(log, "Libp2p Starting"; "peer_id" => %enr.peer_id(), "bandwidth_config" => format!("{}-{}", config.network_load, NetworkLoad::from(config.network_load).name));
|
||||
let discovery_string = if config.disable_discovery {
|
||||
"None".into()
|
||||
} else {
|
||||
config.discovery_port.to_string()
|
||||
};
|
||||
debug!(log, "Attempting to open listening ports"; "address" => ?config.listen_address, "tcp_port" => config.libp2p_port, "udp_port" => discovery_string);
|
||||
|
||||
let (mut swarm, bandwidth) = {
|
||||
// Set up the transport - tcp/ws with noise and mplex
|
||||
let (transport, bandwidth) = build_transport(local_keypair.clone())
|
||||
.map_err(|e| format!("Failed to build transport: {:?}", e))?;
|
||||
|
||||
// Lighthouse network behaviour
|
||||
let behaviour =
|
||||
Behaviour::new(&local_keypair, ctx, network_globals.clone(), &log).await?;
|
||||
|
||||
// use the executor for libp2p
|
||||
struct Executor(task_executor::TaskExecutor);
|
||||
impl libp2p::core::Executor for Executor {
|
||||
fn exec(&self, f: Pin<Box<dyn Future<Output = ()> + Send>>) {
|
||||
self.0.spawn(f, "libp2p");
|
||||
}
|
||||
}
|
||||
|
||||
// sets up the libp2p connection limits
|
||||
let limits = ConnectionLimits::default()
|
||||
.with_max_pending_incoming(Some(5))
|
||||
.with_max_pending_outgoing(Some(16))
|
||||
.with_max_established_incoming(Some(
|
||||
(config.target_peers as f32
|
||||
* (1.0 + PEER_EXCESS_FACTOR - MIN_OUTBOUND_ONLY_FACTOR))
|
||||
.ceil() as u32,
|
||||
))
|
||||
.with_max_established_outgoing(Some(
|
||||
(config.target_peers as f32 * (1.0 + PEER_EXCESS_FACTOR)).ceil() as u32,
|
||||
))
|
||||
.with_max_established(Some(
|
||||
(config.target_peers as f32 * (1.0 + PEER_EXCESS_FACTOR + PRIORITY_PEER_EXCESS))
|
||||
.ceil() as u32,
|
||||
))
|
||||
.with_max_established_per_peer(Some(MAX_CONNECTIONS_PER_PEER));
|
||||
|
||||
(
|
||||
SwarmBuilder::new(transport, behaviour, local_peer_id)
|
||||
.notify_handler_buffer_size(std::num::NonZeroUsize::new(7).expect("Not zero"))
|
||||
.connection_event_buffer_size(64)
|
||||
.connection_limits(limits)
|
||||
.executor(Box::new(Executor(executor)))
|
||||
.build(),
|
||||
bandwidth,
|
||||
)
|
||||
};
|
||||
|
||||
// listen on the specified address
|
||||
let listen_multiaddr = {
|
||||
let mut m = Multiaddr::from(config.listen_address);
|
||||
m.push(Protocol::Tcp(config.libp2p_port));
|
||||
m
|
||||
};
|
||||
|
||||
match Swarm::listen_on(&mut swarm, listen_multiaddr.clone()) {
|
||||
Ok(_) => {
|
||||
let mut log_address = listen_multiaddr;
|
||||
log_address.push(Protocol::P2p(local_peer_id.into()));
|
||||
info!(log, "Listening established"; "address" => %log_address);
|
||||
}
|
||||
Err(err) => {
|
||||
crit!(
|
||||
log,
|
||||
"Unable to listen on libp2p address";
|
||||
"error" => ?err,
|
||||
"listen_multiaddr" => %listen_multiaddr,
|
||||
);
|
||||
return Err("Libp2p was unable to listen on the given listen address.".into());
|
||||
}
|
||||
};
|
||||
|
||||
// helper closure for dialing peers
|
||||
let mut dial = |mut multiaddr: Multiaddr| {
|
||||
// strip the p2p protocol if it exists
|
||||
strip_peer_id(&mut multiaddr);
|
||||
match Swarm::dial(&mut swarm, multiaddr.clone()) {
|
||||
Ok(()) => debug!(log, "Dialing libp2p peer"; "address" => %multiaddr),
|
||||
Err(err) => debug!(
|
||||
log,
|
||||
"Could not connect to peer"; "address" => %multiaddr, "error" => ?err
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
// attempt to connect to user-input libp2p nodes
|
||||
for multiaddr in &config.libp2p_nodes {
|
||||
dial(multiaddr.clone());
|
||||
}
|
||||
|
||||
// attempt to connect to any specified boot-nodes
|
||||
let mut boot_nodes = config.boot_nodes_enr.clone();
|
||||
boot_nodes.dedup();
|
||||
|
||||
for bootnode_enr in boot_nodes {
|
||||
for multiaddr in &bootnode_enr.multiaddr() {
|
||||
// ignore udp multiaddr if it exists
|
||||
let components = multiaddr.iter().collect::<Vec<_>>();
|
||||
if let Protocol::Udp(_) = components[1] {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !network_globals
|
||||
.peers
|
||||
.read()
|
||||
.is_connected_or_dialing(&bootnode_enr.peer_id())
|
||||
{
|
||||
dial(multiaddr.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for multiaddr in &config.boot_nodes_multiaddr {
|
||||
// check TCP support for dialing
|
||||
if multiaddr
|
||||
.iter()
|
||||
.any(|proto| matches!(proto, Protocol::Tcp(_)))
|
||||
{
|
||||
dial(multiaddr.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut subscribed_topics: Vec<GossipKind> = vec![];
|
||||
|
||||
for topic_kind in &config.topics {
|
||||
if swarm.behaviour_mut().subscribe_kind(topic_kind.clone()) {
|
||||
subscribed_topics.push(topic_kind.clone());
|
||||
} else {
|
||||
warn!(log, "Could not subscribe to topic"; "topic" => %topic_kind);
|
||||
}
|
||||
}
|
||||
|
||||
if !subscribed_topics.is_empty() {
|
||||
info!(log, "Subscribed to topics"; "topics" => ?subscribed_topics);
|
||||
}
|
||||
|
||||
let service = Service {
|
||||
swarm,
|
||||
bandwidth,
|
||||
local_peer_id,
|
||||
log,
|
||||
};
|
||||
|
||||
Ok((network_globals, service))
|
||||
}
|
||||
|
||||
/// Sends a request to a peer, with a given Id.
|
||||
pub fn send_request(&mut self, peer_id: PeerId, request_id: AppReqId, request: Request) {
|
||||
self.swarm
|
||||
.behaviour_mut()
|
||||
.send_request(peer_id, request_id, request);
|
||||
}
|
||||
|
||||
/// Informs the peer that their request failed.
|
||||
pub fn respond_with_error(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
id: PeerRequestId,
|
||||
error: RPCResponseErrorCode,
|
||||
reason: String,
|
||||
) {
|
||||
self.swarm
|
||||
.behaviour_mut()
|
||||
.send_error_reponse(peer_id, id, error, reason);
|
||||
}
|
||||
|
||||
/// Report a peer's action.
|
||||
pub fn report_peer(
|
||||
&mut self,
|
||||
peer_id: &PeerId,
|
||||
action: PeerAction,
|
||||
source: ReportSource,
|
||||
msg: &'static str,
|
||||
) {
|
||||
self.swarm
|
||||
.behaviour_mut()
|
||||
.peer_manager_mut()
|
||||
.report_peer(peer_id, action, source, None, msg);
|
||||
}
|
||||
|
||||
/// Disconnect and ban a peer, providing a reason.
|
||||
pub fn goodbye_peer(&mut self, peer_id: &PeerId, reason: GoodbyeReason, source: ReportSource) {
|
||||
self.swarm
|
||||
.behaviour_mut()
|
||||
.goodbye_peer(peer_id, reason, source);
|
||||
}
|
||||
|
||||
/// Sends a response to a peer's request.
|
||||
pub fn send_response(&mut self, peer_id: PeerId, id: PeerRequestId, response: Response<TSpec>) {
|
||||
self.swarm
|
||||
.behaviour_mut()
|
||||
.send_successful_response(peer_id, id, response);
|
||||
}
|
||||
|
||||
pub async fn next_event(&mut self) -> Libp2pEvent<AppReqId, TSpec> {
|
||||
loop {
|
||||
match self.swarm.select_next_some().await {
|
||||
SwarmEvent::Behaviour(behaviour) => {
|
||||
// Handle banning here
|
||||
match &behaviour {
|
||||
BehaviourEvent::PeerBanned(peer_id) => {
|
||||
self.swarm.ban_peer_id(*peer_id);
|
||||
}
|
||||
BehaviourEvent::PeerUnbanned(peer_id) => {
|
||||
self.swarm.unban_peer_id(*peer_id);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return Libp2pEvent::Behaviour(behaviour);
|
||||
}
|
||||
SwarmEvent::ConnectionEstablished {
|
||||
peer_id: _,
|
||||
endpoint: _,
|
||||
num_established: _,
|
||||
concurrent_dial_errors: _,
|
||||
} => {}
|
||||
SwarmEvent::ConnectionClosed {
|
||||
peer_id: _,
|
||||
cause: _,
|
||||
endpoint: _,
|
||||
num_established: _,
|
||||
} => {}
|
||||
SwarmEvent::NewListenAddr { address, .. } => {
|
||||
return Libp2pEvent::NewListenAddr(address)
|
||||
}
|
||||
SwarmEvent::IncomingConnection {
|
||||
local_addr,
|
||||
send_back_addr,
|
||||
} => {
|
||||
trace!(self.log, "Incoming connection"; "our_addr" => %local_addr, "from" => %send_back_addr)
|
||||
}
|
||||
SwarmEvent::IncomingConnectionError {
|
||||
local_addr,
|
||||
send_back_addr,
|
||||
error,
|
||||
} => {
|
||||
debug!(self.log, "Failed incoming connection"; "our_addr" => %local_addr, "from" => %send_back_addr, "error" => %error);
|
||||
}
|
||||
SwarmEvent::BannedPeer { peer_id, .. } => {
|
||||
debug!(self.log, "Banned peer connection rejected"; "peer_id" => %peer_id);
|
||||
}
|
||||
SwarmEvent::OutgoingConnectionError { peer_id, error } => {
|
||||
debug!(self.log, "Failed to dial address"; "peer_id" => ?peer_id, "error" => %error);
|
||||
}
|
||||
SwarmEvent::ExpiredListenAddr { address, .. } => {
|
||||
debug!(self.log, "Listen address expired"; "address" => %address)
|
||||
}
|
||||
SwarmEvent::ListenerClosed {
|
||||
addresses, reason, ..
|
||||
} => {
|
||||
crit!(self.log, "Listener closed"; "addresses" => ?addresses, "reason" => ?reason);
|
||||
if Swarm::listeners(&self.swarm).count() == 0 {
|
||||
return Libp2pEvent::ZeroListeners;
|
||||
}
|
||||
}
|
||||
SwarmEvent::ListenerError { error, .. } => {
|
||||
// this is non fatal, but we still check
|
||||
warn!(self.log, "Listener error"; "error" => ?error);
|
||||
if Swarm::listeners(&self.swarm).count() == 0 {
|
||||
return Libp2pEvent::ZeroListeners;
|
||||
}
|
||||
}
|
||||
SwarmEvent::Dialing(_peer_id) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type BoxedTransport = Boxed<(PeerId, StreamMuxerBox)>;
|
||||
|
||||
/// The implementation supports TCP/IP, WebSockets over TCP/IP, noise as the encryption layer, and
|
||||
/// mplex as the multiplexing layer.
|
||||
fn build_transport(
|
||||
local_private_key: Keypair,
|
||||
) -> std::io::Result<(BoxedTransport, Arc<BandwidthSinks>)> {
|
||||
let tcp = libp2p::tcp::TokioTcpConfig::new().nodelay(true);
|
||||
let transport = libp2p::dns::TokioDnsConfig::system(tcp)?;
|
||||
#[cfg(feature = "libp2p-websocket")]
|
||||
let transport = {
|
||||
let trans_clone = transport.clone();
|
||||
transport.or_transport(libp2p::websocket::WsConfig::new(trans_clone))
|
||||
};
|
||||
|
||||
let (transport, bandwidth) = BandwidthLogging::new(transport);
|
||||
|
||||
// mplex config
|
||||
let mut mplex_config = libp2p::mplex::MplexConfig::new();
|
||||
mplex_config.set_max_buffer_size(256);
|
||||
mplex_config.set_max_buffer_behaviour(libp2p::mplex::MaxBufferBehaviour::Block);
|
||||
|
||||
// yamux config
|
||||
let mut yamux_config = libp2p::yamux::YamuxConfig::default();
|
||||
yamux_config.set_window_update_mode(libp2p::yamux::WindowUpdateMode::on_read());
|
||||
|
||||
// Authentication
|
||||
Ok((
|
||||
transport
|
||||
.upgrade(core::upgrade::Version::V1)
|
||||
.authenticate(generate_noise_config(&local_private_key))
|
||||
.multiplex(core::upgrade::SelectUpgrade::new(
|
||||
yamux_config,
|
||||
mplex_config,
|
||||
))
|
||||
.timeout(Duration::from_secs(10))
|
||||
.boxed(),
|
||||
bandwidth,
|
||||
))
|
||||
}
|
||||
|
||||
// Useful helper functions for debugging. Currently not used in the client.
|
||||
#[allow(dead_code)]
|
||||
fn keypair_from_hex(hex_bytes: &str) -> error::Result<Keypair> {
|
||||
let hex_bytes = if let Some(stripped) = hex_bytes.strip_prefix("0x") {
|
||||
stripped.to_string()
|
||||
} else {
|
||||
hex_bytes.to_string()
|
||||
};
|
||||
|
||||
hex::decode(&hex_bytes)
|
||||
.map_err(|e| format!("Failed to parse p2p secret key bytes: {:?}", e).into())
|
||||
.and_then(keypair_from_bytes)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn keypair_from_bytes(mut bytes: Vec<u8>) -> error::Result<Keypair> {
|
||||
libp2p::core::identity::secp256k1::SecretKey::from_bytes(&mut bytes)
|
||||
.map(|secret| {
|
||||
let keypair: libp2p::core::identity::secp256k1::Keypair = secret.into();
|
||||
Keypair::Secp256k1(keypair)
|
||||
})
|
||||
.map_err(|e| format!("Unable to parse p2p secret key: {:?}", e).into())
|
||||
}
|
||||
|
||||
/// Loads a private key from disk. If this fails, a new key is
|
||||
/// generated and is then saved to disk.
|
||||
///
|
||||
/// Currently only secp256k1 keys are allowed, as these are the only keys supported by discv5.
|
||||
pub fn load_private_key(config: &NetworkConfig, log: &slog::Logger) -> Keypair {
|
||||
// check for key from disk
|
||||
let network_key_f = config.network_dir.join(NETWORK_KEY_FILENAME);
|
||||
if let Ok(mut network_key_file) = File::open(network_key_f.clone()) {
|
||||
let mut key_bytes: Vec<u8> = Vec::with_capacity(36);
|
||||
match network_key_file.read_to_end(&mut key_bytes) {
|
||||
Err(_) => debug!(log, "Could not read network key file"),
|
||||
Ok(_) => {
|
||||
// only accept secp256k1 keys for now
|
||||
if let Ok(secret_key) =
|
||||
libp2p::core::identity::secp256k1::SecretKey::from_bytes(&mut key_bytes)
|
||||
{
|
||||
let kp: libp2p::core::identity::secp256k1::Keypair = secret_key.into();
|
||||
debug!(log, "Loaded network key from disk.");
|
||||
return Keypair::Secp256k1(kp);
|
||||
} else {
|
||||
debug!(log, "Network key file is not a valid secp256k1 key");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if a key could not be loaded from disk, generate a new one and save it
|
||||
let local_private_key = Keypair::generate_secp256k1();
|
||||
if let Keypair::Secp256k1(key) = local_private_key.clone() {
|
||||
let _ = std::fs::create_dir_all(&config.network_dir);
|
||||
match File::create(network_key_f.clone())
|
||||
.and_then(|mut f| f.write_all(&key.secret().to_bytes()))
|
||||
{
|
||||
Ok(_) => {
|
||||
debug!(log, "New network key generated and written to disk");
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
log,
|
||||
"Could not write node key to file: {:?}. error: {}", network_key_f, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
local_private_key
|
||||
}
|
||||
|
||||
/// Generate authenticated XX Noise config from identity keys
|
||||
fn generate_noise_config(
|
||||
identity_keypair: &Keypair,
|
||||
) -> noise::NoiseAuthenticated<noise::XX, noise::X25519Spec, ()> {
|
||||
let static_dh_keys = noise::Keypair::<noise::X25519Spec>::new()
|
||||
.into_authentic(identity_keypair)
|
||||
.expect("signing can fail only once during starting a node");
|
||||
noise::NoiseConfig::xx(static_dh_keys).into_authenticated()
|
||||
}
|
||||
|
||||
/// For a multiaddr that ends with a peer id, this strips this suffix. Rust-libp2p
|
||||
/// only supports dialing to an address without providing the peer id.
|
||||
fn strip_peer_id(addr: &mut Multiaddr) {
|
||||
let last = addr.pop();
|
||||
match last {
|
||||
Some(Protocol::P2p(_)) => {}
|
||||
Some(other) => addr.push(other),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load metadata from persisted file. Return default metadata if loading fails.
|
||||
fn load_or_build_metadata<E: EthSpec>(
|
||||
network_dir: &std::path::Path,
|
||||
log: &slog::Logger,
|
||||
) -> MetaData<E> {
|
||||
// We load a V2 metadata version by default (regardless of current fork)
|
||||
// since a V2 metadata can be converted to V1. The RPC encoder is responsible
|
||||
// for sending the correct metadata version based on the negotiated protocol version.
|
||||
let mut meta_data = MetaDataV2 {
|
||||
seq_number: 0,
|
||||
attnets: EnrAttestationBitfield::<E>::default(),
|
||||
syncnets: EnrSyncCommitteeBitfield::<E>::default(),
|
||||
};
|
||||
// Read metadata from persisted file if available
|
||||
let metadata_path = network_dir.join(METADATA_FILENAME);
|
||||
if let Ok(mut metadata_file) = File::open(metadata_path) {
|
||||
let mut metadata_ssz = Vec::new();
|
||||
if metadata_file.read_to_end(&mut metadata_ssz).is_ok() {
|
||||
// Attempt to read a MetaDataV2 version from the persisted file,
|
||||
// if that fails, read MetaDataV1
|
||||
match MetaDataV2::<E>::from_ssz_bytes(&metadata_ssz) {
|
||||
Ok(persisted_metadata) => {
|
||||
meta_data.seq_number = persisted_metadata.seq_number;
|
||||
// Increment seq number if persisted attnet is not default
|
||||
if persisted_metadata.attnets != meta_data.attnets
|
||||
|| persisted_metadata.syncnets != meta_data.syncnets
|
||||
{
|
||||
meta_data.seq_number += 1;
|
||||
}
|
||||
debug!(log, "Loaded metadata from disk");
|
||||
}
|
||||
Err(_) => {
|
||||
match MetaDataV1::<E>::from_ssz_bytes(&metadata_ssz) {
|
||||
Ok(persisted_metadata) => {
|
||||
let persisted_metadata = MetaData::V1(persisted_metadata);
|
||||
// Increment seq number as the persisted metadata version is updated
|
||||
meta_data.seq_number = *persisted_metadata.seq_number() + 1;
|
||||
debug!(log, "Loaded metadata from disk");
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(
|
||||
log,
|
||||
"Metadata from file could not be decoded";
|
||||
"error" => ?e,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Wrap the MetaData
|
||||
let meta_data = MetaData::V2(meta_data);
|
||||
|
||||
debug!(log, "Metadata sequence number"; "seq_num" => meta_data.seq_number());
|
||||
save_metadata_to_disk(network_dir, meta_data.clone(), log);
|
||||
meta_data
|
||||
}
|
||||
101
beacon_node/lighthouse_network/src/service/api_types.rs
Normal file
101
beacon_node/lighthouse_network/src/service/api_types.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use libp2p::core::connection::ConnectionId;
|
||||
use types::{EthSpec, SignedBeaconBlock};
|
||||
|
||||
use crate::rpc::{
|
||||
methods::{
|
||||
BlocksByRangeRequest, BlocksByRootRequest, OldBlocksByRangeRequest, RPCCodedResponse,
|
||||
RPCResponse, ResponseTermination, StatusMessage,
|
||||
},
|
||||
OutboundRequest, SubstreamId,
|
||||
};
|
||||
|
||||
/// Identifier of requests sent by a peer.
|
||||
pub type PeerRequestId = (ConnectionId, SubstreamId);
|
||||
|
||||
/// Identifier of a request.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RequestId<AppReqId> {
|
||||
Application(AppReqId),
|
||||
Internal,
|
||||
}
|
||||
|
||||
/// The type of RPC requests the Behaviour informs it has received and allows for sending.
|
||||
///
|
||||
// NOTE: This is an application-level wrapper over the lower network level requests that can be
|
||||
// sent. The main difference is the absence of the Ping, Metadata and Goodbye protocols, which don't
|
||||
// leave the Behaviour. For all protocols managed by RPC see `RPCRequest`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Request {
|
||||
/// A Status message.
|
||||
Status(StatusMessage),
|
||||
/// A blocks by range request.
|
||||
BlocksByRange(BlocksByRangeRequest),
|
||||
/// A request blocks root request.
|
||||
BlocksByRoot(BlocksByRootRequest),
|
||||
}
|
||||
|
||||
impl<TSpec: EthSpec> std::convert::From<Request> for OutboundRequest<TSpec> {
|
||||
fn from(req: Request) -> OutboundRequest<TSpec> {
|
||||
match req {
|
||||
Request::BlocksByRoot(r) => OutboundRequest::BlocksByRoot(r),
|
||||
Request::BlocksByRange(BlocksByRangeRequest { start_slot, count }) => {
|
||||
OutboundRequest::BlocksByRange(OldBlocksByRangeRequest {
|
||||
start_slot,
|
||||
count,
|
||||
step: 1,
|
||||
})
|
||||
}
|
||||
Request::Status(s) => OutboundRequest::Status(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of RPC responses the Behaviour informs it has received, and allows for sending.
|
||||
///
|
||||
// NOTE: This is an application-level wrapper over the lower network level responses that can be
|
||||
// sent. The main difference is the absense of Pong and Metadata, which don't leave the
|
||||
// Behaviour. For all protocol reponses managed by RPC see `RPCResponse` and
|
||||
// `RPCCodedResponse`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Response<TSpec: EthSpec> {
|
||||
/// A Status message.
|
||||
Status(StatusMessage),
|
||||
/// A response to a get BLOCKS_BY_RANGE request. A None response signals the end of the batch.
|
||||
BlocksByRange(Option<Arc<SignedBeaconBlock<TSpec>>>),
|
||||
/// A response to a get BLOCKS_BY_ROOT request.
|
||||
BlocksByRoot(Option<Arc<SignedBeaconBlock<TSpec>>>),
|
||||
}
|
||||
|
||||
impl<TSpec: EthSpec> std::convert::From<Response<TSpec>> for RPCCodedResponse<TSpec> {
|
||||
fn from(resp: Response<TSpec>) -> RPCCodedResponse<TSpec> {
|
||||
match resp {
|
||||
Response::BlocksByRoot(r) => match r {
|
||||
Some(b) => RPCCodedResponse::Success(RPCResponse::BlocksByRoot(b)),
|
||||
None => RPCCodedResponse::StreamTermination(ResponseTermination::BlocksByRoot),
|
||||
},
|
||||
Response::BlocksByRange(r) => match r {
|
||||
Some(b) => RPCCodedResponse::Success(RPCResponse::BlocksByRange(b)),
|
||||
None => RPCCodedResponse::StreamTermination(ResponseTermination::BlocksByRange),
|
||||
},
|
||||
Response::Status(s) => RPCCodedResponse::Success(RPCResponse::Status(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<AppReqId: std::fmt::Debug> slog::Value for RequestId<AppReqId> {
|
||||
fn serialize(
|
||||
&self,
|
||||
record: &slog::Record,
|
||||
key: slog::Key,
|
||||
serializer: &mut dyn slog::Serializer,
|
||||
) -> slog::Result {
|
||||
match self {
|
||||
RequestId::Internal => slog::Value::serialize("Behaviour", record, key, serializer),
|
||||
RequestId::Application(ref id) => {
|
||||
slog::Value::serialize(&format_args!("{:?}", id), record, key, serializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
beacon_node/lighthouse_network/src/service/behaviour.rs
Normal file
34
beacon_node/lighthouse_network/src/service/behaviour.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use crate::discovery::Discovery;
|
||||
use crate::peer_manager::PeerManager;
|
||||
use crate::rpc::{ReqId, RPC};
|
||||
use crate::types::SnappyTransform;
|
||||
|
||||
use libp2p::gossipsub::subscription_filter::{
|
||||
MaxCountSubscriptionFilter, WhitelistSubscriptionFilter,
|
||||
};
|
||||
use libp2p::gossipsub::Gossipsub as BaseGossipsub;
|
||||
use libp2p::identify::Identify;
|
||||
use libp2p::swarm::NetworkBehaviour;
|
||||
use libp2p::NetworkBehaviour;
|
||||
use types::EthSpec;
|
||||
|
||||
use super::api_types::RequestId;
|
||||
|
||||
pub type SubscriptionFilter = MaxCountSubscriptionFilter<WhitelistSubscriptionFilter>;
|
||||
pub type Gossipsub = BaseGossipsub<SnappyTransform, SubscriptionFilter>;
|
||||
|
||||
#[derive(NetworkBehaviour)]
|
||||
pub(crate) struct Behaviour<AppReqId: ReqId, TSpec: EthSpec> {
|
||||
/// The routing pub-sub mechanism for eth2.
|
||||
pub gossipsub: Gossipsub,
|
||||
/// The Eth2 RPC specified in the wire-0 protocol.
|
||||
pub eth2_rpc: RPC<RequestId<AppReqId>, TSpec>,
|
||||
/// Discv5 Discovery protocol.
|
||||
pub discovery: Discovery<TSpec>,
|
||||
/// Keep regular connection to peers and disconnect if absent.
|
||||
// NOTE: The id protocol is used for initial interop. This will be removed by mainnet.
|
||||
/// Provides IP addresses and peer information.
|
||||
pub identify: Identify,
|
||||
/// The peer manager that keeps track of peer's reputation and status.
|
||||
pub peer_manager: PeerManager<TSpec>,
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
288
beacon_node/lighthouse_network/src/service/utils.rs
Normal file
288
beacon_node/lighthouse_network/src/service/utils.rs
Normal file
@@ -0,0 +1,288 @@
|
||||
use crate::multiaddr::Protocol;
|
||||
use crate::rpc::{MetaData, MetaDataV1, MetaDataV2};
|
||||
use crate::types::{
|
||||
error, EnrAttestationBitfield, EnrSyncCommitteeBitfield, GossipEncoding, GossipKind,
|
||||
};
|
||||
use crate::{GossipTopic, NetworkConfig};
|
||||
use libp2p::bandwidth::{BandwidthLogging, BandwidthSinks};
|
||||
use libp2p::core::{
|
||||
identity::Keypair, multiaddr::Multiaddr, muxing::StreamMuxerBox, transport::Boxed,
|
||||
};
|
||||
use libp2p::gossipsub::subscription_filter::WhitelistSubscriptionFilter;
|
||||
use libp2p::gossipsub::IdentTopic as Topic;
|
||||
use libp2p::{core, noise, PeerId, Transport};
|
||||
use prometheus_client::registry::Registry;
|
||||
use slog::{debug, warn};
|
||||
use ssz::Decode;
|
||||
use ssz::Encode;
|
||||
use std::collections::HashSet;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use types::{ChainSpec, EnrForkId, EthSpec, ForkContext, SubnetId, SyncSubnetId};
|
||||
|
||||
pub const NETWORK_KEY_FILENAME: &str = "key";
|
||||
/// The maximum simultaneous libp2p connections per peer.
|
||||
pub const MAX_CONNECTIONS_PER_PEER: u32 = 1;
|
||||
/// The filename to store our local metadata.
|
||||
pub const METADATA_FILENAME: &str = "metadata";
|
||||
|
||||
pub struct Context<'a> {
|
||||
pub config: &'a NetworkConfig,
|
||||
pub enr_fork_id: EnrForkId,
|
||||
pub fork_context: Arc<ForkContext>,
|
||||
pub chain_spec: &'a ChainSpec,
|
||||
pub gossipsub_registry: Option<&'a mut Registry>,
|
||||
}
|
||||
|
||||
type BoxedTransport = Boxed<(PeerId, StreamMuxerBox)>;
|
||||
|
||||
/// The implementation supports TCP/IP, WebSockets over TCP/IP, noise as the encryption layer, and
|
||||
/// mplex as the multiplexing layer.
|
||||
pub fn build_transport(
|
||||
local_private_key: Keypair,
|
||||
) -> std::io::Result<(BoxedTransport, Arc<BandwidthSinks>)> {
|
||||
let tcp =
|
||||
libp2p::tcp::TokioTcpTransport::new(libp2p::tcp::GenTcpConfig::default().nodelay(true));
|
||||
let transport = libp2p::dns::TokioDnsConfig::system(tcp)?;
|
||||
#[cfg(feature = "libp2p-websocket")]
|
||||
let transport = {
|
||||
let trans_clone = transport.clone();
|
||||
transport.or_transport(libp2p::websocket::WsConfig::new(trans_clone))
|
||||
};
|
||||
|
||||
let (transport, bandwidth) = BandwidthLogging::new(transport);
|
||||
|
||||
// mplex config
|
||||
let mut mplex_config = libp2p::mplex::MplexConfig::new();
|
||||
mplex_config.set_max_buffer_size(256);
|
||||
mplex_config.set_max_buffer_behaviour(libp2p::mplex::MaxBufferBehaviour::Block);
|
||||
|
||||
// yamux config
|
||||
let mut yamux_config = libp2p::yamux::YamuxConfig::default();
|
||||
yamux_config.set_window_update_mode(libp2p::yamux::WindowUpdateMode::on_read());
|
||||
|
||||
// Authentication
|
||||
Ok((
|
||||
transport
|
||||
.upgrade(core::upgrade::Version::V1)
|
||||
.authenticate(generate_noise_config(&local_private_key))
|
||||
.multiplex(core::upgrade::SelectUpgrade::new(
|
||||
yamux_config,
|
||||
mplex_config,
|
||||
))
|
||||
.timeout(Duration::from_secs(10))
|
||||
.boxed(),
|
||||
bandwidth,
|
||||
))
|
||||
}
|
||||
|
||||
// Useful helper functions for debugging. Currently not used in the client.
|
||||
#[allow(dead_code)]
|
||||
fn keypair_from_hex(hex_bytes: &str) -> error::Result<Keypair> {
|
||||
let hex_bytes = if let Some(stripped) = hex_bytes.strip_prefix("0x") {
|
||||
stripped.to_string()
|
||||
} else {
|
||||
hex_bytes.to_string()
|
||||
};
|
||||
|
||||
hex::decode(&hex_bytes)
|
||||
.map_err(|e| format!("Failed to parse p2p secret key bytes: {:?}", e).into())
|
||||
.and_then(keypair_from_bytes)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn keypair_from_bytes(mut bytes: Vec<u8>) -> error::Result<Keypair> {
|
||||
libp2p::core::identity::secp256k1::SecretKey::from_bytes(&mut bytes)
|
||||
.map(|secret| {
|
||||
let keypair: libp2p::core::identity::secp256k1::Keypair = secret.into();
|
||||
Keypair::Secp256k1(keypair)
|
||||
})
|
||||
.map_err(|e| format!("Unable to parse p2p secret key: {:?}", e).into())
|
||||
}
|
||||
|
||||
/// Loads a private key from disk. If this fails, a new key is
|
||||
/// generated and is then saved to disk.
|
||||
///
|
||||
/// Currently only secp256k1 keys are allowed, as these are the only keys supported by discv5.
|
||||
pub fn load_private_key(config: &NetworkConfig, log: &slog::Logger) -> Keypair {
|
||||
// check for key from disk
|
||||
let network_key_f = config.network_dir.join(NETWORK_KEY_FILENAME);
|
||||
if let Ok(mut network_key_file) = File::open(network_key_f.clone()) {
|
||||
let mut key_bytes: Vec<u8> = Vec::with_capacity(36);
|
||||
match network_key_file.read_to_end(&mut key_bytes) {
|
||||
Err(_) => debug!(log, "Could not read network key file"),
|
||||
Ok(_) => {
|
||||
// only accept secp256k1 keys for now
|
||||
if let Ok(secret_key) =
|
||||
libp2p::core::identity::secp256k1::SecretKey::from_bytes(&mut key_bytes)
|
||||
{
|
||||
let kp: libp2p::core::identity::secp256k1::Keypair = secret_key.into();
|
||||
debug!(log, "Loaded network key from disk.");
|
||||
return Keypair::Secp256k1(kp);
|
||||
} else {
|
||||
debug!(log, "Network key file is not a valid secp256k1 key");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if a key could not be loaded from disk, generate a new one and save it
|
||||
let local_private_key = Keypair::generate_secp256k1();
|
||||
if let Keypair::Secp256k1(key) = local_private_key.clone() {
|
||||
let _ = std::fs::create_dir_all(&config.network_dir);
|
||||
match File::create(network_key_f.clone())
|
||||
.and_then(|mut f| f.write_all(&key.secret().to_bytes()))
|
||||
{
|
||||
Ok(_) => {
|
||||
debug!(log, "New network key generated and written to disk");
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
log,
|
||||
"Could not write node key to file: {:?}. error: {}", network_key_f, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
local_private_key
|
||||
}
|
||||
|
||||
/// Generate authenticated XX Noise config from identity keys
|
||||
fn generate_noise_config(
|
||||
identity_keypair: &Keypair,
|
||||
) -> noise::NoiseAuthenticated<noise::XX, noise::X25519Spec, ()> {
|
||||
let static_dh_keys = noise::Keypair::<noise::X25519Spec>::new()
|
||||
.into_authentic(identity_keypair)
|
||||
.expect("signing can fail only once during starting a node");
|
||||
noise::NoiseConfig::xx(static_dh_keys).into_authenticated()
|
||||
}
|
||||
|
||||
/// For a multiaddr that ends with a peer id, this strips this suffix. Rust-libp2p
|
||||
/// only supports dialing to an address without providing the peer id.
|
||||
pub fn strip_peer_id(addr: &mut Multiaddr) {
|
||||
let last = addr.pop();
|
||||
match last {
|
||||
Some(Protocol::P2p(_)) => {}
|
||||
Some(other) => addr.push(other),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load metadata from persisted file. Return default metadata if loading fails.
|
||||
pub fn load_or_build_metadata<E: EthSpec>(
|
||||
network_dir: &std::path::Path,
|
||||
log: &slog::Logger,
|
||||
) -> MetaData<E> {
|
||||
// We load a V2 metadata version by default (regardless of current fork)
|
||||
// since a V2 metadata can be converted to V1. The RPC encoder is responsible
|
||||
// for sending the correct metadata version based on the negotiated protocol version.
|
||||
let mut meta_data = MetaDataV2 {
|
||||
seq_number: 0,
|
||||
attnets: EnrAttestationBitfield::<E>::default(),
|
||||
syncnets: EnrSyncCommitteeBitfield::<E>::default(),
|
||||
};
|
||||
// Read metadata from persisted file if available
|
||||
let metadata_path = network_dir.join(METADATA_FILENAME);
|
||||
if let Ok(mut metadata_file) = File::open(metadata_path) {
|
||||
let mut metadata_ssz = Vec::new();
|
||||
if metadata_file.read_to_end(&mut metadata_ssz).is_ok() {
|
||||
// Attempt to read a MetaDataV2 version from the persisted file,
|
||||
// if that fails, read MetaDataV1
|
||||
match MetaDataV2::<E>::from_ssz_bytes(&metadata_ssz) {
|
||||
Ok(persisted_metadata) => {
|
||||
meta_data.seq_number = persisted_metadata.seq_number;
|
||||
// Increment seq number if persisted attnet is not default
|
||||
if persisted_metadata.attnets != meta_data.attnets
|
||||
|| persisted_metadata.syncnets != meta_data.syncnets
|
||||
{
|
||||
meta_data.seq_number += 1;
|
||||
}
|
||||
debug!(log, "Loaded metadata from disk");
|
||||
}
|
||||
Err(_) => {
|
||||
match MetaDataV1::<E>::from_ssz_bytes(&metadata_ssz) {
|
||||
Ok(persisted_metadata) => {
|
||||
let persisted_metadata = MetaData::V1(persisted_metadata);
|
||||
// Increment seq number as the persisted metadata version is updated
|
||||
meta_data.seq_number = *persisted_metadata.seq_number() + 1;
|
||||
debug!(log, "Loaded metadata from disk");
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(
|
||||
log,
|
||||
"Metadata from file could not be decoded";
|
||||
"error" => ?e,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Wrap the MetaData
|
||||
let meta_data = MetaData::V2(meta_data);
|
||||
|
||||
debug!(log, "Metadata sequence number"; "seq_num" => meta_data.seq_number());
|
||||
save_metadata_to_disk(network_dir, meta_data.clone(), log);
|
||||
meta_data
|
||||
}
|
||||
|
||||
/// Creates a whitelist topic filter that covers all possible topics using the given set of
|
||||
/// possible fork digests.
|
||||
pub(crate) fn create_whitelist_filter(
|
||||
possible_fork_digests: Vec<[u8; 4]>,
|
||||
attestation_subnet_count: u64,
|
||||
sync_committee_subnet_count: u64,
|
||||
) -> WhitelistSubscriptionFilter {
|
||||
let mut possible_hashes = HashSet::new();
|
||||
for fork_digest in possible_fork_digests {
|
||||
let mut add = |kind| {
|
||||
let topic: Topic =
|
||||
GossipTopic::new(kind, GossipEncoding::SSZSnappy, fork_digest).into();
|
||||
possible_hashes.insert(topic.hash());
|
||||
};
|
||||
|
||||
use GossipKind::*;
|
||||
add(BeaconBlock);
|
||||
add(BeaconAggregateAndProof);
|
||||
add(VoluntaryExit);
|
||||
add(ProposerSlashing);
|
||||
add(AttesterSlashing);
|
||||
add(SignedContributionAndProof);
|
||||
for id in 0..attestation_subnet_count {
|
||||
add(Attestation(SubnetId::new(id)));
|
||||
}
|
||||
for id in 0..sync_committee_subnet_count {
|
||||
add(SyncCommitteeMessage(SyncSubnetId::new(id)));
|
||||
}
|
||||
}
|
||||
WhitelistSubscriptionFilter(possible_hashes)
|
||||
}
|
||||
|
||||
/// Persist metadata to disk
|
||||
pub(crate) fn save_metadata_to_disk<E: EthSpec>(
|
||||
dir: &Path,
|
||||
metadata: MetaData<E>,
|
||||
log: &slog::Logger,
|
||||
) {
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
match File::create(dir.join(METADATA_FILENAME))
|
||||
.and_then(|mut f| f.write_all(&metadata.as_ssz_bytes()))
|
||||
{
|
||||
Ok(_) => {
|
||||
debug!(log, "Metadata written to disk");
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
log,
|
||||
"Could not write metadata to disk";
|
||||
"file" => format!("{:?}{:?}", dir, METADATA_FILENAME),
|
||||
"error" => %e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user