Connects the attestation service to network components (#961)

* Sends attestations to the attestation service for processing

* Adds 'attnets' field to local ENR

* Adds ENR bitfield modification logic

* Link attestation service to discovery

- Updates discv5
- Links discover events to discovery
- Support for ENRBitfield

* Adds discovery config params, correct warnings

* Rust fmt fixes

* Correct tests
This commit is contained in:
Age Manning
2020-03-25 22:18:06 +11:00
committed by GitHub
parent fbcf0f8e2e
commit 6ca4f4709b
16 changed files with 381 additions and 149 deletions

View File

@@ -5,20 +5,28 @@ use libp2p::core::identity::Keypair;
use libp2p::discv5::enr::{CombinedKey, EnrBuilder};
use slog::{debug, warn};
use ssz::Encode;
use ssz_types::BitVector;
use std::convert::TryInto;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::str::FromStr;
use types::{EnrForkId, EthSpec};
/// The ENR field specifying the fork id.
pub const ETH2_ENR_KEY: &'static str = "eth2";
/// The ENR field specifying the subnet bitfield.
pub const BITFIELD_ENR_KEY: &'static str = "attnets";
/// Loads an ENR from file if it exists and matches the current NodeId and sequence number. If none
/// exists, generates a new one.
///
/// If an ENR exists, with the same NodeId, this function checks to see if the loaded ENR from
/// disk is suitable to use, otherwise we increment our newly generated ENR's sequence number.
pub fn build_or_load_enr(
pub fn build_or_load_enr<T: EthSpec>(
local_key: Keypair,
config: &NetworkConfig,
enr_fork_id: EnrForkId,
log: &slog::Logger,
) -> Result<Enr, String> {
// Build the local ENR.
@@ -28,7 +36,7 @@ pub fn build_or_load_enr(
.try_into()
.map_err(|_| "Invalid key type for ENR records")?;
let mut local_enr = build_enr(&enr_key, config)?;
let mut local_enr = build_enr::<T>(&enr_key, config, enr_fork_id)?;
let enr_f = config.network_dir.join(ENR_FILENAME);
if let Ok(mut enr_file) = File::open(enr_f.clone()) {
@@ -68,7 +76,11 @@ pub fn build_or_load_enr(
}
/// Builds a lighthouse ENR given a `NetworkConfig`.
fn build_enr(enr_key: &CombinedKey, config: &NetworkConfig) -> Result<Enr, String> {
fn build_enr<T: EthSpec>(
enr_key: &CombinedKey,
config: &NetworkConfig,
enr_fork_id: EnrForkId,
) -> Result<Enr, String> {
let mut builder = EnrBuilder::new("v4");
if let Some(enr_address) = config.enr_address {
builder.ip(enr_address);
@@ -82,7 +94,12 @@ fn build_enr(enr_key: &CombinedKey, config: &NetworkConfig) -> Result<Enr, Strin
builder.tcp(tcp_port);
// set the `eth2` field on our ENR
builder.add_value("eth2".into(), config.enr_fork_id.as_ssz_bytes());
builder.add_value(ETH2_ENR_KEY.into(), enr_fork_id.as_ssz_bytes());
// set the "attnets" field on our ENR
let bitfield = BitVector::<T::SubnetBitfieldLength>::new();
builder.add_value(BITFIELD_ENR_KEY.into(), bitfield.as_ssz_bytes());
builder
.tcp(config.libp2p_port)
@@ -97,9 +114,13 @@ fn compare_enr(local_enr: &Enr, disk_enr: &Enr) -> bool {
(local_enr.ip().is_none() || local_enr.ip() == disk_enr.ip())
// tcp ports must match
&& local_enr.tcp() == disk_enr.tcp()
&& local_enr.get("eth2") == disk_enr.get("eth2")
// must match on the same fork
&& local_enr.get(ETH2_ENR_KEY) == disk_enr.get(ETH2_ENR_KEY)
// take preference over disk udp port if one is not specified
&& (local_enr.udp().is_none() || local_enr.udp() == disk_enr.udp())
// we need the BITFIELD_ENR_KEY key to match, otherwise we use a new ENR. This will likely only
// be true for non-validating nodes
&& local_enr.get(BITFIELD_ENR_KEY) == disk_enr.get(BITFIELD_ENR_KEY)
}
/// Saves an ENR to disk

View File

@@ -2,8 +2,10 @@
mod enr_helpers;
use crate::metrics;
use crate::types::EnrBitfield;
use crate::Enr;
use crate::{error, NetworkConfig, NetworkGlobals, PeerInfo};
use enr_helpers::{BITFIELD_ENR_KEY, ETH2_ENR_KEY};
use futures::prelude::*;
use libp2p::core::{identity::Keypair, ConnectedPoint, Multiaddr, PeerId};
use libp2p::discv5::enr::NodeId;
@@ -11,7 +13,8 @@ use libp2p::discv5::{Discv5, Discv5Event};
use libp2p::multiaddr::Protocol;
use libp2p::swarm::{NetworkBehaviour, NetworkBehaviourAction, PollParameters, ProtocolsHandler};
use slog::{debug, info, warn};
use ssz::Encode;
use ssz::{Decode, Encode};
use ssz_types::BitVector;
use std::collections::HashSet;
use std::net::SocketAddr;
use std::path::Path;
@@ -19,7 +22,7 @@ use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::timer::Delay;
use types::{EnrForkId, EthSpec};
use types::{EnrForkId, EthSpec, SubnetId};
/// Maximum seconds before searching for extra peers.
const MAX_TIME_BETWEEN_PEER_SEARCHES: u64 = 120;
@@ -27,6 +30,8 @@ const MAX_TIME_BETWEEN_PEER_SEARCHES: u64 = 120;
const INITIAL_SEARCH_DELAY: u64 = 5;
/// Local ENR storage filename.
const ENR_FILENAME: &str = "enr.dat";
/// Number of peers we'd like to have connected to a given long-lived subnet.
const TARGET_SUBNET_PEERS: u64 = 3;
/// Lighthouse discovery behaviour. This provides peer management and discovery using the Discv5
/// libp2p protocol.
@@ -66,13 +71,15 @@ impl<TSubstream, TSpec: EthSpec> Discovery<TSubstream, TSpec> {
pub fn new(
local_key: &Keypair,
config: &NetworkConfig,
enr_fork_id: EnrForkId,
network_globals: Arc<NetworkGlobals<TSpec>>,
log: &slog::Logger,
) -> error::Result<Self> {
let log = log.clone();
// checks if current ENR matches that found on disk
let local_enr = enr_helpers::build_or_load_enr(local_key.clone(), config, &log)?;
let local_enr =
enr_helpers::build_or_load_enr::<TSpec>(local_key.clone(), config, enr_fork_id, &log)?;
*network_globals.local_enr.write() = Some(local_enr.clone());
@@ -162,6 +169,51 @@ impl<TSubstream, TSpec: EthSpec> Discovery<TSubstream, TSpec> {
self.discovery.enr_entries()
}
/// Adds/Removes a subnet from the ENR Bitfield
pub fn update_enr_bitfield(&mut self, subnet_id: SubnetId, value: bool) -> Result<(), String> {
let id = *subnet_id as usize;
let local_enr = self.discovery.local_enr();
let bitfield_bytes = local_enr
.get(BITFIELD_ENR_KEY)
.ok_or_else(|| "ENR bitfield non-existent")?;
let mut current_bitfield =
BitVector::<TSpec::SubnetBitfieldLength>::from_ssz_bytes(bitfield_bytes)
.map_err(|_| "Could not decode local ENR SSZ bitfield")?;
if id >= current_bitfield.len() {
return Err(format!(
"Subnet id: {} is outside the ENR bitfield length: {}",
id,
current_bitfield.len()
));
}
if current_bitfield
.get(id)
.map_err(|_| String::from("Subnet ID out of bounds"))?
== value
{
return Err(format!(
"Subnet id: {} already in the local ENR already has value: {}",
id, value
));
}
// set the subnet bitfield in the ENR
current_bitfield
.set(id, value)
.map_err(|_| String::from("Subnet ID out of bounds, could not set subnet ID"))?;
// insert the bitfield into the ENR record
let _ = self
.discovery
.enr_insert(BITFIELD_ENR_KEY, current_bitfield.as_ssz_bytes());
Ok(())
}
/// Updates the `eth2` field of our local ENR.
pub fn update_eth2_enr(&mut self, enr_fork_id: EnrForkId) {
// to avoid having a reference to the spec constant, for the logging we assume
@@ -180,7 +232,7 @@ impl<TSubstream, TSpec: EthSpec> Discovery<TSubstream, TSpec> {
let _ = self
.discovery
.enr_insert("eth2".into(), enr_fork_id.as_ssz_bytes())
.enr_insert(ETH2_ENR_KEY.into(), enr_fork_id.as_ssz_bytes())
.map_err(|e| {
warn!(
self.log,
@@ -190,6 +242,39 @@ impl<TSubstream, TSpec: EthSpec> Discovery<TSubstream, TSpec> {
});
}
/// A request to find peers on a given subnet.
// TODO: This logic should be improved with added sophistication in peer management
// This currently checks for currently connected peers and if we don't have
// PEERS_WANTED_BEFORE_DISCOVERY connected to a given subnet we search for more.
pub fn peers_request(&mut self, subnet_id: SubnetId) {
// TODO: Add PeerManager struct to do this loop for us
let peers_on_subnet = self
.network_globals
.connected_peer_set
.read()
.values()
.fold(0, |found_peers, peer_info| {
if peer_info.on_subnet(subnet_id) {
found_peers + 1
} else {
found_peers
}
});
if peers_on_subnet < TARGET_SUBNET_PEERS {
debug!(self.log, "Searching for peers for subnet";
"subnet_id" => *subnet_id,
"connected_peers_on_subnet" => peers_on_subnet,
"target_subnet_peers" => TARGET_SUBNET_PEERS
);
// TODO: Update to predicate search
self.find_peers();
}
}
/* Internal Functions */
/// Search for new peers using the underlying discovery mechanism.
fn find_peers(&mut self) {
// pick a random NodeId
@@ -217,11 +302,35 @@ where
}
fn inject_connected(&mut self, peer_id: PeerId, _endpoint: ConnectedPoint) {
// TODO: Search for a known ENR once discv5 is updated.
// Find ENR info about a peer if possible.
let mut peer_info = PeerInfo::new();
if let Some(enr) = self.discovery.enr_of_peer(&peer_id) {
let bitfield = match enr.get(BITFIELD_ENR_KEY) {
Some(bitfield_bytes) => {
match EnrBitfield::<TSpec>::from_ssz_bytes(bitfield_bytes) {
Ok(bitfield) => bitfield,
Err(e) => {
warn!(self.log, "Peer had invalid ENR bitfield";
"peer_id" => format!("{}", peer_id),
"error" => format!("{:?}", e));
return;
}
}
}
None => {
warn!(self.log, "Peer has no ENR bitfield";
"peer_id" => format!("{}", peer_id));
return;
}
};
peer_info.enr_bitfield = Some(bitfield);
}
self.network_globals
.connected_peer_set
.write()
.insert(peer_id, PeerInfo::new());
.insert(peer_id, peer_info);
// TODO: Drop peers if over max_peer limit
metrics::inc_counter(&metrics::PEER_CONNECT_EVENT_COUNT);