mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-30 11:13:34 +00:00
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:
@@ -1,6 +1,7 @@
|
||||
use crate::behaviour::gossipsub_scoring_parameters::{
|
||||
lighthouse_gossip_thresholds, PeerScoreSettings,
|
||||
};
|
||||
use crate::config::gossipsub_config;
|
||||
use crate::discovery::{subnet_predicate, Discovery, DiscoveryEvent, TARGET_SUBNET_PEERS};
|
||||
use crate::peer_manager::{
|
||||
score::ReportSource, ConnectionDirection, PeerManager, PeerManagerEvent,
|
||||
@@ -8,7 +9,7 @@ use crate::peer_manager::{
|
||||
use crate::rpc::*;
|
||||
use crate::service::METADATA_FILENAME;
|
||||
use crate::types::{
|
||||
subnet_id_from_topic_hash, GossipEncoding, GossipKind, GossipTopic, SnappyTransform,
|
||||
subnet_from_topic_hash, GossipEncoding, GossipKind, GossipTopic, SnappyTransform, Subnet,
|
||||
SubnetDiscovery,
|
||||
};
|
||||
use crate::Eth2Enr;
|
||||
@@ -42,7 +43,10 @@ use std::{
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use types::{ChainSpec, EnrForkId, EthSpec, SignedBeaconBlock, Slot, SubnetId};
|
||||
use types::{
|
||||
consts::altair::SYNC_COMMITTEE_SUBNET_COUNT, ChainSpec, EnrForkId, EthSpec, ForkContext,
|
||||
SignedBeaconBlock, Slot, SubnetId, SyncSubnetId,
|
||||
};
|
||||
|
||||
pub mod gossipsub_scoring_parameters;
|
||||
|
||||
@@ -157,6 +161,8 @@ pub struct Behaviour<TSpec: EthSpec> {
|
||||
/// Directory where metadata is stored.
|
||||
#[behaviour(ignore)]
|
||||
network_dir: PathBuf,
|
||||
#[behaviour(ignore)]
|
||||
fork_context: Arc<ForkContext>,
|
||||
/// Gossipsub score parameters.
|
||||
#[behaviour(ignore)]
|
||||
score_settings: PeerScoreSettings<TSpec>,
|
||||
@@ -172,9 +178,10 @@ pub struct Behaviour<TSpec: EthSpec> {
|
||||
impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
pub async fn new(
|
||||
local_key: &Keypair,
|
||||
config: &NetworkConfig,
|
||||
mut config: NetworkConfig,
|
||||
network_globals: Arc<NetworkGlobals<TSpec>>,
|
||||
log: &slog::Logger,
|
||||
fork_context: Arc<ForkContext>,
|
||||
chain_spec: &ChainSpec,
|
||||
) -> error::Result<Self> {
|
||||
let behaviour_log = log.new(o!());
|
||||
@@ -191,7 +198,8 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
};
|
||||
|
||||
// Build and start the discovery sub-behaviour
|
||||
let mut discovery = Discovery::new(local_key, config, network_globals.clone(), log).await?;
|
||||
let mut discovery =
|
||||
Discovery::new(local_key, &config, network_globals.clone(), log).await?;
|
||||
// start searching for peers
|
||||
discovery.discover_peers();
|
||||
|
||||
@@ -201,13 +209,19 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
.eth2()
|
||||
.expect("Local ENR must have a fork id");
|
||||
|
||||
let possible_fork_digests = vec![enr_fork_id.fork_digest];
|
||||
let possible_fork_digests = fork_context.all_fork_digests();
|
||||
let filter = MaxCountSubscriptionFilter {
|
||||
filter: Self::create_whitelist_filter(possible_fork_digests, 64), //TODO change this to a constant
|
||||
filter: Self::create_whitelist_filter(
|
||||
possible_fork_digests,
|
||||
chain_spec.attestation_subnet_count,
|
||||
SYNC_COMMITTEE_SUBNET_COUNT,
|
||||
),
|
||||
max_subscribed_topics: 200, //TODO change this to a constant
|
||||
max_subscriptions_per_request: 100, //this is according to the current go implementation
|
||||
};
|
||||
|
||||
config.gs_config = gossipsub_config(fork_context.clone());
|
||||
|
||||
// Build and configure the Gossipsub behaviour
|
||||
let snappy_transform = SnappyTransform::new(config.gs_config.max_transmit_size());
|
||||
let mut gossipsub = Gossipsub::new_with_subscription_filter_and_transform(
|
||||
@@ -247,11 +261,11 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
Ok(Behaviour {
|
||||
// Sub-behaviours
|
||||
gossipsub,
|
||||
eth2_rpc: RPC::new(log.clone()),
|
||||
eth2_rpc: RPC::new(fork_context.clone(), log.clone()),
|
||||
discovery,
|
||||
identify: Identify::new(identify_config),
|
||||
// Auxiliary fields
|
||||
peer_manager: PeerManager::new(config, network_globals.clone(), log).await?,
|
||||
peer_manager: PeerManager::new(&config, network_globals.clone(), log).await?,
|
||||
events: VecDeque::new(),
|
||||
internal_events: VecDeque::new(),
|
||||
network_globals,
|
||||
@@ -260,6 +274,7 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
network_dir: config.network_dir.clone(),
|
||||
log: behaviour_log,
|
||||
score_settings,
|
||||
fork_context,
|
||||
update_gossipsub_scores,
|
||||
})
|
||||
}
|
||||
@@ -311,28 +326,20 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
self.unsubscribe(gossip_topic)
|
||||
}
|
||||
|
||||
/// Subscribes to a specific subnet id;
|
||||
pub fn subscribe_to_subnet(&mut self, subnet_id: SubnetId) -> bool {
|
||||
let topic = GossipTopic::new(
|
||||
subnet_id.into(),
|
||||
GossipEncoding::default(),
|
||||
self.enr_fork_id.fork_digest,
|
||||
);
|
||||
self.subscribe(topic)
|
||||
}
|
||||
|
||||
/// Un-Subscribes from a specific subnet id;
|
||||
pub fn unsubscribe_from_subnet(&mut self, subnet_id: SubnetId) -> bool {
|
||||
let topic = GossipTopic::new(
|
||||
subnet_id.into(),
|
||||
GossipEncoding::default(),
|
||||
self.enr_fork_id.fork_digest,
|
||||
);
|
||||
self.unsubscribe(topic)
|
||||
/// Unsubscribe from all topics that doesn't have the given fork_digest
|
||||
pub fn unsubscribe_from_fork_topics_except(&mut self, except: [u8; 4]) {
|
||||
let subscriptions = self.network_globals.gossipsub_subscriptions.read().clone();
|
||||
for topic in subscriptions
|
||||
.iter()
|
||||
.filter(|topic| topic.fork_digest != except)
|
||||
.cloned()
|
||||
{
|
||||
self.unsubscribe(topic);
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribes to a gossipsub topic.
|
||||
fn subscribe(&mut self, topic: GossipTopic) -> bool {
|
||||
pub fn subscribe(&mut self, topic: GossipTopic) -> bool {
|
||||
// update the network globals
|
||||
self.network_globals
|
||||
.gossipsub_subscriptions
|
||||
@@ -354,7 +361,7 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
}
|
||||
|
||||
/// Unsubscribe from a gossipsub topic.
|
||||
fn unsubscribe(&mut self, topic: GossipTopic) -> bool {
|
||||
pub fn unsubscribe(&mut self, topic: GossipTopic) -> bool {
|
||||
// update the network globals
|
||||
self.network_globals
|
||||
.gossipsub_subscriptions
|
||||
@@ -537,15 +544,15 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
self.discovery.add_enr(enr);
|
||||
}
|
||||
|
||||
/// Updates a subnet value to the ENR bitfield.
|
||||
/// Updates a subnet value to the ENR attnets/syncnets bitfield.
|
||||
///
|
||||
/// The `value` is `true` if a subnet is being added and false otherwise.
|
||||
pub fn update_enr_subnet(&mut self, subnet_id: SubnetId, value: bool) {
|
||||
pub fn update_enr_subnet(&mut self, subnet_id: Subnet, value: bool) {
|
||||
if let Err(e) = self.discovery.update_enr_bitfield(subnet_id, value) {
|
||||
crit!(self.log, "Could not update ENR bitfield"; "error" => e);
|
||||
}
|
||||
// update the local meta data which informs our peers of the update during PINGS
|
||||
self.update_metadata();
|
||||
self.update_metadata_bitfields();
|
||||
}
|
||||
|
||||
/// Attempts to discover new peers for a given subnet. The `min_ttl` gives the time at which we
|
||||
@@ -564,20 +571,24 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
self.network_globals
|
||||
.peers
|
||||
.write()
|
||||
.extend_peers_on_subnet(s.subnet_id, min_ttl);
|
||||
.extend_peers_on_subnet(&s.subnet, min_ttl);
|
||||
if let Subnet::SyncCommittee(sync_subnet) = s.subnet {
|
||||
self.peer_manager_mut()
|
||||
.add_sync_subnet(sync_subnet, min_ttl);
|
||||
}
|
||||
}
|
||||
// Already have target number of peers, no need for subnet discovery
|
||||
let peers_on_subnet = self
|
||||
.network_globals
|
||||
.peers
|
||||
.read()
|
||||
.good_peers_on_subnet(s.subnet_id)
|
||||
.good_peers_on_subnet(s.subnet)
|
||||
.count();
|
||||
if peers_on_subnet >= TARGET_SUBNET_PEERS {
|
||||
trace!(
|
||||
self.log,
|
||||
"Discovery query ignored";
|
||||
"subnet_id" => ?s.subnet_id,
|
||||
"subnet" => ?s.subnet,
|
||||
"reason" => "Already connected to desired peers",
|
||||
"connected_peers_on_subnet" => peers_on_subnet,
|
||||
"target_subnet_peers" => TARGET_SUBNET_PEERS,
|
||||
@@ -587,7 +598,7 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
// If we connect to the cached peers before the discovery query starts, then we potentially
|
||||
// save a costly discovery query.
|
||||
} else {
|
||||
self.dial_cached_enrs_in_subnet(s.subnet_id);
|
||||
self.dial_cached_enrs_in_subnet(s.subnet);
|
||||
true
|
||||
}
|
||||
})
|
||||
@@ -603,26 +614,6 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
pub fn update_fork_version(&mut self, enr_fork_id: EnrForkId) {
|
||||
self.discovery.update_eth2_enr(enr_fork_id.clone());
|
||||
|
||||
// unsubscribe from all gossip topics and re-subscribe to their new fork counterparts
|
||||
let subscribed_topics = self
|
||||
.network_globals
|
||||
.gossipsub_subscriptions
|
||||
.read()
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<GossipTopic>>();
|
||||
|
||||
// unsubscribe from all topics
|
||||
for topic in &subscribed_topics {
|
||||
self.unsubscribe(topic.clone());
|
||||
}
|
||||
|
||||
// re-subscribe modifying the fork version
|
||||
for mut topic in subscribed_topics {
|
||||
*topic.digest() = enr_fork_id.fork_digest;
|
||||
self.subscribe(topic);
|
||||
}
|
||||
|
||||
// update the local reference
|
||||
self.enr_fork_id = enr_fork_id;
|
||||
}
|
||||
@@ -630,18 +621,28 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
/* Private internal functions */
|
||||
|
||||
/// Updates the current meta data of the node to match the local ENR.
|
||||
fn update_metadata(&mut self) {
|
||||
fn update_metadata_bitfields(&mut self) {
|
||||
let local_attnets = self
|
||||
.discovery
|
||||
.local_enr()
|
||||
.bitfield::<TSpec>()
|
||||
.expect("Local discovery must have bitfield");
|
||||
.attestation_bitfield::<TSpec>()
|
||||
.expect("Local discovery must have attestation bitfield");
|
||||
|
||||
let local_syncnets = self
|
||||
.discovery
|
||||
.local_enr()
|
||||
.sync_committee_bitfield::<TSpec>()
|
||||
.expect("Local discovery must have sync committee bitfield");
|
||||
|
||||
{
|
||||
// write lock scope
|
||||
let mut meta_data = self.network_globals.local_metadata.write();
|
||||
meta_data.seq_number += 1;
|
||||
meta_data.attnets = local_attnets;
|
||||
|
||||
*meta_data.seq_number_mut() += 1;
|
||||
*meta_data.attnets_mut() = local_attnets;
|
||||
if let Ok(syncnets) = meta_data.syncnets_mut() {
|
||||
*syncnets = local_syncnets;
|
||||
}
|
||||
}
|
||||
// Save the updated metadata to disk
|
||||
save_metadata_to_disk(
|
||||
@@ -654,7 +655,7 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
/// Sends a Ping request to the peer.
|
||||
fn ping(&mut self, id: RequestId, peer_id: PeerId) {
|
||||
let ping = crate::rpc::Ping {
|
||||
data: self.network_globals.local_metadata.read().seq_number,
|
||||
data: *self.network_globals.local_metadata.read().seq_number(),
|
||||
};
|
||||
trace!(self.log, "Sending Ping"; "request_id" => id, "peer_id" => %peer_id);
|
||||
|
||||
@@ -665,7 +666,7 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
/// Sends a Pong response to the peer.
|
||||
fn pong(&mut self, id: PeerRequestId, peer_id: PeerId) {
|
||||
let ping = crate::rpc::Ping {
|
||||
data: self.network_globals.local_metadata.read().seq_number,
|
||||
data: *self.network_globals.local_metadata.read().seq_number(),
|
||||
};
|
||||
trace!(self.log, "Sending Pong"; "request_id" => id.1, "peer_id" => %peer_id);
|
||||
let event = RPCCodedResponse::Success(RPCResponse::Pong(ping));
|
||||
@@ -724,8 +725,8 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
|
||||
/// Dial cached enrs in discovery service that are in the given `subnet_id` and aren't
|
||||
/// in Connected, Dialing or Banned state.
|
||||
fn dial_cached_enrs_in_subnet(&mut self, subnet_id: SubnetId) {
|
||||
let predicate = subnet_predicate::<TSpec>(vec![subnet_id], &self.log);
|
||||
fn dial_cached_enrs_in_subnet(&mut self, subnet: Subnet) {
|
||||
let predicate = subnet_predicate::<TSpec>(vec![subnet], &self.log);
|
||||
let peers_to_dial: Vec<PeerId> = self
|
||||
.discovery
|
||||
.cached_enrs()
|
||||
@@ -752,6 +753,7 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
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 {
|
||||
@@ -767,9 +769,13 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
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)
|
||||
}
|
||||
@@ -792,9 +798,9 @@ impl<TSpec: EthSpec> NetworkBehaviourEventProcess<GossipsubEvent> for Behaviour<
|
||||
} => {
|
||||
// Note: We are keeping track here of the peer that sent us the message, not the
|
||||
// peer that originally published the message.
|
||||
match PubsubMessage::decode(&gs_msg.topic, &gs_msg.data) {
|
||||
match PubsubMessage::decode(&gs_msg.topic, &gs_msg.data, &self.fork_context) {
|
||||
Err(e) => {
|
||||
debug!(self.log, "Could not decode gossipsub message"; "error" => e);
|
||||
debug!(self.log, "Could not decode gossipsub message"; "topic" => ?gs_msg.topic,"error" => e);
|
||||
//reject the message
|
||||
if let Err(e) = self.gossipsub.report_message_validation_result(
|
||||
&id,
|
||||
@@ -816,12 +822,12 @@ impl<TSpec: EthSpec> NetworkBehaviourEventProcess<GossipsubEvent> for Behaviour<
|
||||
}
|
||||
}
|
||||
GossipsubEvent::Subscribed { peer_id, topic } => {
|
||||
if let Some(subnet_id) = subnet_id_from_topic_hash(&topic) {
|
||||
if let Some(subnet_id) = subnet_from_topic_hash(&topic) {
|
||||
self.peer_manager.add_subscription(&peer_id, subnet_id);
|
||||
}
|
||||
}
|
||||
GossipsubEvent::Unsubscribed { peer_id, topic } => {
|
||||
if let Some(subnet_id) = subnet_id_from_topic_hash(&topic) {
|
||||
if let Some(subnet_id) = subnet_from_topic_hash(&topic) {
|
||||
self.peer_manager.remove_subscription(&peer_id, subnet_id);
|
||||
}
|
||||
}
|
||||
@@ -1089,6 +1095,10 @@ impl<TSpec: EthSpec> Behaviour<TSpec> {
|
||||
// Peer manager has requested a discovery query for more peers.
|
||||
self.discovery.discover_peers();
|
||||
}
|
||||
PeerManagerEvent::DiscoverSubnetPeers(subnets_to_discover) => {
|
||||
// Peer manager has requested a subnet discovery query for more peers.
|
||||
self.discover_subnet_peers(subnets_to_discover);
|
||||
}
|
||||
PeerManagerEvent::Ping(peer_id) => {
|
||||
// send a ping request to this peer
|
||||
self.ping(RequestId::Behaviour, peer_id);
|
||||
|
||||
@@ -12,7 +12,9 @@ use libp2p::Multiaddr;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use types::{ForkContext, ForkName};
|
||||
|
||||
/// The maximum transmit size of gossip messages in bytes.
|
||||
pub const GOSSIP_MAX_SIZE: usize = 1_048_576;
|
||||
@@ -109,47 +111,9 @@ impl Default for Config {
|
||||
.join(DEFAULT_BEACON_NODE_DIR)
|
||||
.join(DEFAULT_NETWORK_DIR);
|
||||
|
||||
// The function used to generate a gossipsub message id
|
||||
// We use the first 8 bytes of SHA256(data) for content addressing
|
||||
let fast_gossip_message_id = |message: &RawGossipsubMessage| {
|
||||
FastMessageId::from(&Sha256::digest(&message.data)[..8])
|
||||
};
|
||||
|
||||
fn prefix(prefix: [u8; 4], data: &[u8]) -> Vec<u8> {
|
||||
let mut vec = Vec::with_capacity(prefix.len() + data.len());
|
||||
vec.extend_from_slice(&prefix);
|
||||
vec.extend_from_slice(data);
|
||||
vec
|
||||
}
|
||||
|
||||
let gossip_message_id = |message: &GossipsubMessage| {
|
||||
MessageId::from(
|
||||
&Sha256::digest(prefix(MESSAGE_DOMAIN_VALID_SNAPPY, &message.data).as_slice())
|
||||
[..20],
|
||||
)
|
||||
};
|
||||
|
||||
// gossipsub configuration
|
||||
// Note: The topics by default are sent as plain strings. Hashes are an optional
|
||||
// parameter.
|
||||
// Note: Using the default config here. Use `gossipsub_config` function for getting
|
||||
// Lighthouse specific configuration for gossipsub.
|
||||
let gs_config = GossipsubConfigBuilder::default()
|
||||
.max_transmit_size(GOSSIP_MAX_SIZE)
|
||||
.heartbeat_interval(Duration::from_millis(700))
|
||||
.mesh_n(8)
|
||||
.mesh_n_low(MESH_N_LOW)
|
||||
.mesh_n_high(12)
|
||||
.gossip_lazy(6)
|
||||
.fanout_ttl(Duration::from_secs(60))
|
||||
.history_length(12)
|
||||
.max_messages_per_rpc(Some(500)) // Responses to IWANT can be quite large
|
||||
.history_gossip(3)
|
||||
.validate_messages() // require validation before propagation
|
||||
.validation_mode(ValidationMode::Anonymous)
|
||||
// prevent duplicates for 550 heartbeats(700millis * 550) = 385 secs
|
||||
.duplicate_cache_time(Duration::from_secs(385))
|
||||
.message_id_fn(gossip_message_id)
|
||||
.fast_message_id_fn(fast_gossip_message_id)
|
||||
.allow_self_origin(true)
|
||||
.build()
|
||||
.expect("valid gossipsub configuration");
|
||||
|
||||
@@ -209,3 +173,65 @@ impl Default for Config {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a Lighthouse specific `GossipsubConfig` where the `message_id_fn` depends on the current fork.
|
||||
pub fn gossipsub_config(fork_context: Arc<ForkContext>) -> GossipsubConfig {
|
||||
// The function used to generate a gossipsub message id
|
||||
// We use the first 8 bytes of SHA256(data) for content addressing
|
||||
let fast_gossip_message_id =
|
||||
|message: &RawGossipsubMessage| FastMessageId::from(&Sha256::digest(&message.data)[..8]);
|
||||
fn prefix(
|
||||
prefix: [u8; 4],
|
||||
message: &GossipsubMessage,
|
||||
fork_context: Arc<ForkContext>,
|
||||
) -> Vec<u8> {
|
||||
let topic_bytes = message.topic.as_str().as_bytes();
|
||||
match fork_context.current_fork() {
|
||||
ForkName::Altair => {
|
||||
let topic_len_bytes = topic_bytes.len().to_le_bytes();
|
||||
let mut vec = Vec::with_capacity(
|
||||
prefix.len() + topic_len_bytes.len() + topic_bytes.len() + message.data.len(),
|
||||
);
|
||||
vec.extend_from_slice(&prefix);
|
||||
vec.extend_from_slice(&topic_len_bytes);
|
||||
vec.extend_from_slice(topic_bytes);
|
||||
vec.extend_from_slice(&message.data);
|
||||
vec
|
||||
}
|
||||
ForkName::Base => {
|
||||
let mut vec = Vec::with_capacity(prefix.len() + message.data.len());
|
||||
vec.extend_from_slice(&prefix);
|
||||
vec.extend_from_slice(&message.data);
|
||||
vec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let gossip_message_id = move |message: &GossipsubMessage| {
|
||||
MessageId::from(
|
||||
&Sha256::digest(
|
||||
prefix(MESSAGE_DOMAIN_VALID_SNAPPY, message, fork_context.clone()).as_slice(),
|
||||
)[..20],
|
||||
)
|
||||
};
|
||||
GossipsubConfigBuilder::default()
|
||||
.max_transmit_size(GOSSIP_MAX_SIZE)
|
||||
.heartbeat_interval(Duration::from_millis(700))
|
||||
.mesh_n(8)
|
||||
.mesh_n_low(MESH_N_LOW)
|
||||
.mesh_n_high(12)
|
||||
.gossip_lazy(6)
|
||||
.fanout_ttl(Duration::from_secs(60))
|
||||
.history_length(12)
|
||||
.max_messages_per_rpc(Some(500)) // Responses to IWANT can be quite large
|
||||
.history_gossip(3)
|
||||
.validate_messages() // require validation before propagation
|
||||
.validation_mode(ValidationMode::Anonymous)
|
||||
// prevent duplicates for 550 heartbeats(700millis * 550) = 385 secs
|
||||
.duplicate_cache_time(Duration::from_secs(385))
|
||||
.message_id_fn(gossip_message_id)
|
||||
.fast_message_id_fn(fast_gossip_message_id)
|
||||
.allow_self_origin(true)
|
||||
.build()
|
||||
.expect("valid gossipsub configuration")
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ pub use discv5::enr::{self, CombinedKey, EnrBuilder};
|
||||
|
||||
use super::enr_ext::CombinedKeyExt;
|
||||
use super::ENR_FILENAME;
|
||||
use crate::types::{Enr, EnrBitfield};
|
||||
use crate::types::{Enr, EnrAttestationBitfield, EnrSyncCommitteeBitfield};
|
||||
use crate::NetworkConfig;
|
||||
use discv5::enr::EnrKey;
|
||||
use libp2p::core::identity::Keypair;
|
||||
@@ -19,25 +19,47 @@ use types::{EnrForkId, EthSpec};
|
||||
|
||||
/// The ENR field specifying the fork id.
|
||||
pub const ETH2_ENR_KEY: &str = "eth2";
|
||||
/// The ENR field specifying the subnet bitfield.
|
||||
pub const BITFIELD_ENR_KEY: &str = "attnets";
|
||||
/// The ENR field specifying the attestation subnet bitfield.
|
||||
pub const ATTESTATION_BITFIELD_ENR_KEY: &str = "attnets";
|
||||
/// The ENR field specifying the sync committee subnet bitfield.
|
||||
pub const SYNC_COMMITTEE_BITFIELD_ENR_KEY: &str = "syncnets";
|
||||
|
||||
/// Extension trait for ENR's within Eth2.
|
||||
pub trait Eth2Enr {
|
||||
/// The subnet bitfield associated with the ENR.
|
||||
fn bitfield<TSpec: EthSpec>(&self) -> Result<EnrBitfield<TSpec>, &'static str>;
|
||||
/// The attestation subnet bitfield associated with the ENR.
|
||||
fn attestation_bitfield<TSpec: EthSpec>(
|
||||
&self,
|
||||
) -> Result<EnrAttestationBitfield<TSpec>, &'static str>;
|
||||
|
||||
/// The sync committee subnet bitfield associated with the ENR.
|
||||
fn sync_committee_bitfield<TSpec: EthSpec>(
|
||||
&self,
|
||||
) -> Result<EnrSyncCommitteeBitfield<TSpec>, &'static str>;
|
||||
|
||||
fn eth2(&self) -> Result<EnrForkId, &'static str>;
|
||||
}
|
||||
|
||||
impl Eth2Enr for Enr {
|
||||
fn bitfield<TSpec: EthSpec>(&self) -> Result<EnrBitfield<TSpec>, &'static str> {
|
||||
fn attestation_bitfield<TSpec: EthSpec>(
|
||||
&self,
|
||||
) -> Result<EnrAttestationBitfield<TSpec>, &'static str> {
|
||||
let bitfield_bytes = self
|
||||
.get(BITFIELD_ENR_KEY)
|
||||
.ok_or("ENR bitfield non-existent")?;
|
||||
.get(ATTESTATION_BITFIELD_ENR_KEY)
|
||||
.ok_or("ENR attestation bitfield non-existent")?;
|
||||
|
||||
BitVector::<TSpec::SubnetBitfieldLength>::from_ssz_bytes(bitfield_bytes)
|
||||
.map_err(|_| "Could not decode the ENR SSZ bitfield")
|
||||
.map_err(|_| "Could not decode the ENR attnets bitfield")
|
||||
}
|
||||
|
||||
fn sync_committee_bitfield<TSpec: EthSpec>(
|
||||
&self,
|
||||
) -> Result<EnrSyncCommitteeBitfield<TSpec>, &'static str> {
|
||||
let bitfield_bytes = self
|
||||
.get(SYNC_COMMITTEE_BITFIELD_ENR_KEY)
|
||||
.ok_or("ENR sync committee bitfield non-existent")?;
|
||||
|
||||
BitVector::<TSpec::SyncCommitteeSubnetCount>::from_ssz_bytes(bitfield_bytes)
|
||||
.map_err(|_| "Could not decode the ENR syncnets bitfield")
|
||||
}
|
||||
|
||||
fn eth2(&self) -> Result<EnrForkId, &'static str> {
|
||||
@@ -151,7 +173,12 @@ pub fn build_enr<T: EthSpec>(
|
||||
// set the "attnets" field on our ENR
|
||||
let bitfield = BitVector::<T::SubnetBitfieldLength>::new();
|
||||
|
||||
builder.add_value(BITFIELD_ENR_KEY, &bitfield.as_ssz_bytes());
|
||||
builder.add_value(ATTESTATION_BITFIELD_ENR_KEY, &bitfield.as_ssz_bytes());
|
||||
|
||||
// set the "syncnets" field on our ENR
|
||||
let bitfield = BitVector::<T::SyncCommitteeSubnetCount>::new();
|
||||
|
||||
builder.add_value(SYNC_COMMITTEE_BITFIELD_ENR_KEY, &bitfield.as_ssz_bytes());
|
||||
|
||||
builder
|
||||
.build(enr_key)
|
||||
@@ -169,9 +196,10 @@ fn compare_enr(local_enr: &Enr, disk_enr: &Enr) -> bool {
|
||||
&& 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)
|
||||
// we need the ATTESTATION_BITFIELD_ENR_KEY and SYNC_COMMITTEE_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(ATTESTATION_BITFIELD_ENR_KEY) == disk_enr.get(ATTESTATION_BITFIELD_ENR_KEY)
|
||||
&& local_enr.get(SYNC_COMMITTEE_BITFIELD_ENR_KEY) == disk_enr.get(SYNC_COMMITTEE_BITFIELD_ENR_KEY)
|
||||
}
|
||||
|
||||
/// Loads enr from the given directory
|
||||
|
||||
@@ -8,31 +8,28 @@ pub mod enr_ext;
|
||||
|
||||
// Allow external use of the lighthouse ENR builder
|
||||
use crate::{config, metrics};
|
||||
use crate::{error, Enr, NetworkConfig, NetworkGlobals, SubnetDiscovery};
|
||||
use crate::{error, Enr, NetworkConfig, NetworkGlobals, Subnet, SubnetDiscovery};
|
||||
use discv5::{enr::NodeId, Discv5, Discv5Event};
|
||||
pub use enr::{
|
||||
build_enr, create_enr_builder_from_config, load_enr_from_disk, use_or_load_enr, CombinedKey,
|
||||
Eth2Enr,
|
||||
};
|
||||
use enr::{BITFIELD_ENR_KEY, ETH2_ENR_KEY};
|
||||
pub use enr_ext::{peer_id_to_node_id, CombinedKeyExt, EnrExt};
|
||||
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;
|
||||
pub use libp2p::{
|
||||
core::{
|
||||
connection::ConnectionId,
|
||||
identity::{Keypair, PublicKey},
|
||||
ConnectedPoint, Multiaddr, PeerId,
|
||||
},
|
||||
core::{connection::ConnectionId, ConnectedPoint, Multiaddr, PeerId},
|
||||
swarm::{
|
||||
protocols_handler::ProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction as NBAction,
|
||||
NotifyHandler, PollParameters, SubstreamProtocol,
|
||||
},
|
||||
};
|
||||
use lru::LruCache;
|
||||
use slog::{crit, debug, error, info, warn};
|
||||
use ssz::{Decode, Encode};
|
||||
use ssz_types::BitVector;
|
||||
use slog::{crit, debug, error, info, trace, warn};
|
||||
use ssz::Encode;
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
net::{IpAddr, SocketAddr},
|
||||
@@ -43,7 +40,7 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
use types::{EnrForkId, EthSpec, SubnetId};
|
||||
use types::{EnrForkId, EthSpec};
|
||||
|
||||
mod subnet_predicate;
|
||||
pub use subnet_predicate::subnet_predicate;
|
||||
@@ -77,13 +74,26 @@ pub enum DiscoveryEvent {
|
||||
SocketUpdated(SocketAddr),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct SubnetQuery {
|
||||
subnet_id: SubnetId,
|
||||
subnet: Subnet,
|
||||
min_ttl: Option<Instant>,
|
||||
retries: usize,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SubnetQuery {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let min_ttl_secs = self
|
||||
.min_ttl
|
||||
.map(|ttl| ttl.saturating_duration_since(Instant::now()).as_secs());
|
||||
f.debug_struct("SubnetQuery")
|
||||
.field("subnet", &self.subnet)
|
||||
.field("min_ttl_secs", &min_ttl_secs)
|
||||
.field("retries", &self.retries)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum QueryType {
|
||||
/// We are searching for subnet peers.
|
||||
@@ -335,13 +345,13 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
if !self.started {
|
||||
return;
|
||||
}
|
||||
debug!(
|
||||
trace!(
|
||||
self.log,
|
||||
"Making discovery query for subnets";
|
||||
"subnets" => ?subnets_to_discover.iter().map(|s| s.subnet_id).collect::<Vec<_>>()
|
||||
"subnets" => ?subnets_to_discover.iter().map(|s| s.subnet).collect::<Vec<_>>()
|
||||
);
|
||||
for subnet in subnets_to_discover {
|
||||
self.add_subnet_query(subnet.subnet_id, subnet.min_ttl, 0);
|
||||
self.add_subnet_query(subnet.subnet, subnet.min_ttl, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,42 +436,84 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Adds/Removes a subnet from the ENR attnets/syncnets Bitfield
|
||||
pub fn update_enr_bitfield(&mut self, subnet: Subnet, value: bool) -> Result<(), String> {
|
||||
let local_enr = self.discv5.local_enr();
|
||||
let mut current_bitfield = local_enr.bitfield::<TSpec>()?;
|
||||
|
||||
if id >= current_bitfield.len() {
|
||||
return Err(format!(
|
||||
"Subnet id: {} is outside the ENR bitfield length: {}",
|
||||
id,
|
||||
current_bitfield.len()
|
||||
));
|
||||
match subnet {
|
||||
Subnet::Attestation(id) => {
|
||||
let id = *id as usize;
|
||||
let mut current_bitfield = local_enr.attestation_bitfield::<TSpec>()?;
|
||||
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
|
||||
self.discv5
|
||||
.enr_insert(
|
||||
ATTESTATION_BITFIELD_ENR_KEY,
|
||||
¤t_bitfield.as_ssz_bytes(),
|
||||
)
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
}
|
||||
Subnet::SyncCommittee(id) => {
|
||||
let id = *id as usize;
|
||||
let mut current_bitfield = local_enr.sync_committee_bitfield::<TSpec>()?;
|
||||
|
||||
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
|
||||
self.discv5
|
||||
.enr_insert(
|
||||
SYNC_COMMITTEE_BITFIELD_ENR_KEY,
|
||||
¤t_bitfield.as_ssz_bytes(),
|
||||
)
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
self.discv5
|
||||
.enr_insert(BITFIELD_ENR_KEY, ¤t_bitfield.as_ssz_bytes())
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
// replace the global version
|
||||
*self.network_globals.local_enr.write() = self.discv5.local_enr();
|
||||
|
||||
@@ -547,7 +599,7 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
|
||||
/// Adds a subnet query if one doesn't exist. If a subnet query already exists, this
|
||||
/// updates the min_ttl field.
|
||||
fn add_subnet_query(&mut self, subnet_id: SubnetId, min_ttl: Option<Instant>, retries: usize) {
|
||||
fn add_subnet_query(&mut self, subnet: Subnet, min_ttl: Option<Instant>, retries: usize) {
|
||||
// remove the entry and complete the query if greater than the maximum search count
|
||||
if retries > MAX_DISCOVERY_RETRY {
|
||||
debug!(
|
||||
@@ -562,7 +614,7 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
let mut found = false;
|
||||
for query in self.queued_queries.iter_mut() {
|
||||
if let QueryType::Subnet(ref mut subnet_query) = query {
|
||||
if subnet_query.subnet_id == subnet_id {
|
||||
if subnet_query.subnet == subnet {
|
||||
if subnet_query.min_ttl < min_ttl {
|
||||
subnet_query.min_ttl = min_ttl;
|
||||
}
|
||||
@@ -577,12 +629,12 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
if !found {
|
||||
// Set up the query and add it to the queue
|
||||
let query = QueryType::Subnet(SubnetQuery {
|
||||
subnet_id,
|
||||
subnet,
|
||||
min_ttl,
|
||||
retries,
|
||||
});
|
||||
// update the metrics and insert into the queue.
|
||||
debug!(self.log, "Queuing subnet query"; "subnet" => *subnet_id, "retries" => retries);
|
||||
trace!(self.log, "Queuing subnet query"; "subnet" => ?subnet, "retries" => retries);
|
||||
self.queued_queries.push_back(query);
|
||||
metrics::set_gauge(&metrics::DISCOVERY_QUEUE, self.queued_queries.len() as i64);
|
||||
}
|
||||
@@ -636,11 +688,6 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
// This query is for searching for peers of a particular subnet
|
||||
// Drain subnet_queries so we can re-use it as we continue to process the queue
|
||||
let grouped_queries: Vec<SubnetQuery> = subnet_queries.drain(..).collect();
|
||||
debug!(
|
||||
self.log,
|
||||
"Starting grouped subnet query";
|
||||
"subnets" => ?grouped_queries.iter().map(|q| q.subnet_id).collect::<Vec<_>>(),
|
||||
);
|
||||
self.start_subnet_query(grouped_queries);
|
||||
processed = true;
|
||||
}
|
||||
@@ -661,7 +708,7 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
|
||||
/// Runs a discovery request for a given group of subnets.
|
||||
fn start_subnet_query(&mut self, subnet_queries: Vec<SubnetQuery>) {
|
||||
let mut filtered_subnet_ids: Vec<SubnetId> = Vec::new();
|
||||
let mut filtered_subnets: Vec<Subnet> = Vec::new();
|
||||
|
||||
// find subnet queries that are still necessary
|
||||
let filtered_subnet_queries: Vec<SubnetQuery> = subnet_queries
|
||||
@@ -672,7 +719,7 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
.network_globals
|
||||
.peers
|
||||
.read()
|
||||
.good_peers_on_subnet(subnet_query.subnet_id)
|
||||
.good_peers_on_subnet(subnet_query.subnet)
|
||||
.count();
|
||||
|
||||
if peers_on_subnet >= TARGET_SUBNET_PEERS {
|
||||
@@ -685,16 +732,13 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
}
|
||||
|
||||
let target_peers = TARGET_SUBNET_PEERS - peers_on_subnet;
|
||||
debug!(self.log, "Discovery query started for subnet";
|
||||
"subnet_id" => *subnet_query.subnet_id,
|
||||
trace!(self.log, "Discovery query started for subnet";
|
||||
"subnet_query" => ?subnet_query,
|
||||
"connected_peers_on_subnet" => peers_on_subnet,
|
||||
"target_subnet_peers" => TARGET_SUBNET_PEERS,
|
||||
"peers_to_find" => target_peers,
|
||||
"attempt" => subnet_query.retries,
|
||||
"min_ttl" => ?subnet_query.min_ttl,
|
||||
);
|
||||
|
||||
filtered_subnet_ids.push(subnet_query.subnet_id);
|
||||
filtered_subnets.push(subnet_query.subnet);
|
||||
true
|
||||
})
|
||||
.collect();
|
||||
@@ -702,8 +746,13 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
// Only start a discovery query if we have a subnet to look for.
|
||||
if !filtered_subnet_queries.is_empty() {
|
||||
// build the subnet predicate as a combination of the eth2_fork_predicate and the subnet predicate
|
||||
let subnet_predicate = subnet_predicate::<TSpec>(filtered_subnet_ids, &self.log);
|
||||
let subnet_predicate = subnet_predicate::<TSpec>(filtered_subnets, &self.log);
|
||||
|
||||
debug!(
|
||||
self.log,
|
||||
"Starting grouped subnet query";
|
||||
"subnets" => ?filtered_subnet_queries,
|
||||
);
|
||||
self.start_query(
|
||||
GroupedQueryType::Subnet(filtered_subnet_queries),
|
||||
TARGET_PEERS_FOR_GROUPED_QUERY,
|
||||
@@ -798,17 +847,13 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
}
|
||||
}
|
||||
GroupedQueryType::Subnet(queries) => {
|
||||
let subnets_searched_for: Vec<SubnetId> =
|
||||
queries.iter().map(|query| query.subnet_id).collect();
|
||||
let subnets_searched_for: Vec<Subnet> =
|
||||
queries.iter().map(|query| query.subnet).collect();
|
||||
match query_result.1 {
|
||||
Ok(r) if r.is_empty() => {
|
||||
debug!(self.log, "Grouped subnet discovery query yielded no results."; "subnets_searched_for" => ?subnets_searched_for);
|
||||
queries.iter().for_each(|query| {
|
||||
self.add_subnet_query(
|
||||
query.subnet_id,
|
||||
query.min_ttl,
|
||||
query.retries + 1,
|
||||
);
|
||||
self.add_subnet_query(query.subnet, query.min_ttl, query.retries + 1);
|
||||
})
|
||||
}
|
||||
Ok(r) => {
|
||||
@@ -824,15 +869,11 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
// Map each subnet query's min_ttl to the set of ENR's returned for that subnet.
|
||||
queries.iter().for_each(|query| {
|
||||
// A subnet query has completed. Add back to the queue, incrementing retries.
|
||||
self.add_subnet_query(
|
||||
query.subnet_id,
|
||||
query.min_ttl,
|
||||
query.retries + 1,
|
||||
);
|
||||
self.add_subnet_query(query.subnet, query.min_ttl, query.retries + 1);
|
||||
|
||||
// Check the specific subnet against the enr
|
||||
let subnet_predicate =
|
||||
subnet_predicate::<TSpec>(vec![query.subnet_id], &self.log);
|
||||
subnet_predicate::<TSpec>(vec![query.subnet], &self.log);
|
||||
|
||||
r.iter()
|
||||
.filter(|enr| subnet_predicate(enr))
|
||||
@@ -1037,11 +1078,11 @@ impl<TSpec: EthSpec> NetworkBehaviour for Discovery<TSpec> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::rpc::methods::MetaData;
|
||||
use crate::rpc::methods::{MetaData, MetaDataV2};
|
||||
use enr::EnrBuilder;
|
||||
use slog::{o, Drain};
|
||||
use std::net::UdpSocket;
|
||||
use types::MinimalEthSpec;
|
||||
use types::{BitVector, MinimalEthSpec, SubnetId};
|
||||
|
||||
type E = MinimalEthSpec;
|
||||
|
||||
@@ -1076,10 +1117,11 @@ mod tests {
|
||||
enr,
|
||||
9000,
|
||||
9000,
|
||||
MetaData {
|
||||
MetaData::V2(MetaDataV2 {
|
||||
seq_number: 0,
|
||||
attnets: Default::default(),
|
||||
},
|
||||
syncnets: Default::default(),
|
||||
}),
|
||||
vec![],
|
||||
&log,
|
||||
);
|
||||
@@ -1093,12 +1135,12 @@ mod tests {
|
||||
let mut discovery = build_discovery().await;
|
||||
let now = Instant::now();
|
||||
let mut subnet_query = SubnetQuery {
|
||||
subnet_id: SubnetId::new(1),
|
||||
subnet: Subnet::Attestation(SubnetId::new(1)),
|
||||
min_ttl: Some(now),
|
||||
retries: 0,
|
||||
};
|
||||
discovery.add_subnet_query(
|
||||
subnet_query.subnet_id,
|
||||
subnet_query.subnet,
|
||||
subnet_query.min_ttl,
|
||||
subnet_query.retries,
|
||||
);
|
||||
@@ -1109,7 +1151,7 @@ mod tests {
|
||||
|
||||
// New query should replace old query
|
||||
subnet_query.min_ttl = Some(now + Duration::from_secs(1));
|
||||
discovery.add_subnet_query(subnet_query.subnet_id, subnet_query.min_ttl, 1);
|
||||
discovery.add_subnet_query(subnet_query.subnet, subnet_query.min_ttl, 1);
|
||||
|
||||
subnet_query.retries += 1;
|
||||
|
||||
@@ -1122,7 +1164,7 @@ mod tests {
|
||||
// Retries > MAX_DISCOVERY_RETRY must return immediately without adding
|
||||
// anything.
|
||||
discovery.add_subnet_query(
|
||||
subnet_query.subnet_id,
|
||||
subnet_query.subnet,
|
||||
subnet_query.min_ttl,
|
||||
MAX_DISCOVERY_RETRY + 1,
|
||||
);
|
||||
@@ -1140,7 +1182,7 @@ mod tests {
|
||||
|
||||
let now = Instant::now();
|
||||
let subnet_query = SubnetQuery {
|
||||
subnet_id: SubnetId::new(1),
|
||||
subnet: Subnet::Attestation(SubnetId::new(1)),
|
||||
min_ttl: Some(now + Duration::from_secs(10)),
|
||||
retries: 0,
|
||||
};
|
||||
@@ -1174,7 +1216,7 @@ mod tests {
|
||||
bitfield.set(id, true).unwrap();
|
||||
}
|
||||
|
||||
builder.add_value(BITFIELD_ENR_KEY, &bitfield.as_ssz_bytes());
|
||||
builder.add_value(ATTESTATION_BITFIELD_ENR_KEY, &bitfield.as_ssz_bytes());
|
||||
builder.build(&enr_key).unwrap()
|
||||
}
|
||||
|
||||
@@ -1187,12 +1229,12 @@ mod tests {
|
||||
|
||||
let query = GroupedQueryType::Subnet(vec![
|
||||
SubnetQuery {
|
||||
subnet_id: SubnetId::new(1),
|
||||
subnet: Subnet::Attestation(SubnetId::new(1)),
|
||||
min_ttl: instant1,
|
||||
retries: 0,
|
||||
},
|
||||
SubnetQuery {
|
||||
subnet_id: SubnetId::new(2),
|
||||
subnet: Subnet::Attestation(SubnetId::new(2)),
|
||||
min_ttl: instant2,
|
||||
retries: 0,
|
||||
},
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
///! The subnet predicate used for searching for a particular subnet.
|
||||
use super::*;
|
||||
use crate::types::{EnrAttestationBitfield, EnrSyncCommitteeBitfield};
|
||||
use slog::trace;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Returns the predicate for a given subnet.
|
||||
pub fn subnet_predicate<TSpec>(
|
||||
subnet_ids: Vec<SubnetId>,
|
||||
subnets: Vec<Subnet>,
|
||||
log: &slog::Logger,
|
||||
) -> impl Fn(&Enr) -> bool + Send
|
||||
where
|
||||
@@ -14,39 +15,33 @@ where
|
||||
let log_clone = log.clone();
|
||||
|
||||
move |enr: &Enr| {
|
||||
if let Some(bitfield_bytes) = enr.get(BITFIELD_ENR_KEY) {
|
||||
let bitfield = match BitVector::<TSpec::SubnetBitfieldLength>::from_ssz_bytes(
|
||||
bitfield_bytes,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
warn!(log_clone, "Could not decode ENR bitfield for peer"; "peer_id" => format!("{}", enr.peer_id()), "error" => format!("{:?}", e));
|
||||
return false;
|
||||
}
|
||||
let attestation_bitfield: EnrAttestationBitfield<TSpec> =
|
||||
match enr.attestation_bitfield::<TSpec>() {
|
||||
Ok(b) => b,
|
||||
Err(_e) => return false,
|
||||
};
|
||||
|
||||
let matches: Vec<&SubnetId> = subnet_ids
|
||||
.iter()
|
||||
.filter(|id| bitfield.get(**id.deref() as usize).unwrap_or(false))
|
||||
.collect();
|
||||
// Pre-fork/fork-boundary enrs may not contain a syncnets field.
|
||||
// Don't return early here
|
||||
let sync_committee_bitfield: Result<EnrSyncCommitteeBitfield<TSpec>, _> =
|
||||
enr.sync_committee_bitfield::<TSpec>();
|
||||
|
||||
if matches.is_empty() {
|
||||
trace!(
|
||||
log_clone,
|
||||
"Peer found but not on any of the desired subnets";
|
||||
"peer_id" => %enr.peer_id()
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
trace!(
|
||||
log_clone,
|
||||
"Peer found on desired subnet(s)";
|
||||
"peer_id" => %enr.peer_id(),
|
||||
"subnets" => ?matches.as_slice()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
let predicate = subnets.iter().any(|subnet| match subnet {
|
||||
Subnet::Attestation(s) => attestation_bitfield
|
||||
.get(*s.deref() as usize)
|
||||
.unwrap_or(false),
|
||||
Subnet::SyncCommittee(s) => sync_committee_bitfield
|
||||
.as_ref()
|
||||
.map_or(false, |b| b.get(*s.deref() as usize).unwrap_or(false)),
|
||||
});
|
||||
|
||||
if !predicate {
|
||||
trace!(
|
||||
log_clone,
|
||||
"Peer found but not on any of the desired subnets";
|
||||
"peer_id" => %enr.peer_id()
|
||||
);
|
||||
}
|
||||
false
|
||||
predicate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,10 @@ impl<'de> Deserialize<'de> for PeerIdSerialized {
|
||||
}
|
||||
}
|
||||
|
||||
pub use crate::types::{error, Enr, GossipTopic, NetworkGlobals, PubsubMessage, SubnetDiscovery};
|
||||
pub use crate::types::{
|
||||
error, Enr, EnrSyncCommitteeBitfield, GossipTopic, NetworkGlobals, PubsubMessage, Subnet,
|
||||
SubnetDiscovery,
|
||||
};
|
||||
pub use behaviour::{BehaviourEvent, Gossipsub, PeerRequestId, Request, Response};
|
||||
pub use config::Config as NetworkConfig;
|
||||
pub use discovery::{CombinedKeyExt, EnrExt, Eth2Enr};
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -181,16 +181,18 @@ where
|
||||
mod tests {
|
||||
use super::super::ssz_snappy::*;
|
||||
use super::*;
|
||||
use crate::rpc::methods::StatusMessage;
|
||||
use crate::rpc::protocol::*;
|
||||
use snap::write::FrameEncoder;
|
||||
use ssz::Encode;
|
||||
use std::io::Write;
|
||||
use types::{Epoch, Hash256, Slot};
|
||||
|
||||
use std::sync::Arc;
|
||||
use types::{ForkContext, Hash256};
|
||||
use unsigned_varint::codec::Uvi;
|
||||
|
||||
type Spec = types::MainnetEthSpec;
|
||||
|
||||
fn fork_context() -> ForkContext {
|
||||
ForkContext::new::<Spec>(types::Slot::new(0), Hash256::zero(), &Spec::default_spec())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_status_message() {
|
||||
let message = hex::decode("0054ff060000734e615070590032000006e71e7b54989925efd6c9cbcb8ceb9b5f71216f5137282bf6a1e3b50f64e42d6c7fb347abe07eb0db8200000005029e2800").unwrap();
|
||||
@@ -200,8 +202,9 @@ mod tests {
|
||||
let snappy_protocol_id =
|
||||
ProtocolId::new(Protocol::Status, Version::V1, Encoding::SSZSnappy);
|
||||
|
||||
let fork_context = Arc::new(fork_context());
|
||||
let mut snappy_outbound_codec =
|
||||
SSZSnappyOutboundCodec::<Spec>::new(snappy_protocol_id, 1_048_576);
|
||||
SSZSnappyOutboundCodec::<Spec>::new(snappy_protocol_id, 1_048_576, fork_context);
|
||||
|
||||
// remove response code
|
||||
let mut snappy_buf = buf.clone();
|
||||
@@ -233,8 +236,10 @@ mod tests {
|
||||
|
||||
let snappy_protocol_id =
|
||||
ProtocolId::new(Protocol::Status, Version::V1, Encoding::SSZSnappy);
|
||||
|
||||
let fork_context = Arc::new(fork_context());
|
||||
let mut snappy_outbound_codec =
|
||||
SSZSnappyOutboundCodec::<Spec>::new(snappy_protocol_id, 1_048_576);
|
||||
SSZSnappyOutboundCodec::<Spec>::new(snappy_protocol_id, 1_048_576, fork_context);
|
||||
|
||||
let snappy_decoded_message = snappy_outbound_codec.decode(&mut dst).unwrap_err();
|
||||
|
||||
@@ -260,80 +265,34 @@ mod tests {
|
||||
// Response limits
|
||||
let limit = protocol_id.rpc_response_limits::<Spec>();
|
||||
let mut max = encode_len(limit.max + 1);
|
||||
let mut codec = SSZSnappyOutboundCodec::<Spec>::new(protocol_id.clone(), 1_048_576);
|
||||
let fork_context = Arc::new(fork_context());
|
||||
let mut codec = SSZSnappyOutboundCodec::<Spec>::new(
|
||||
protocol_id.clone(),
|
||||
1_048_576,
|
||||
fork_context.clone(),
|
||||
);
|
||||
assert_eq!(codec.decode(&mut max).unwrap_err(), RPCError::InvalidData);
|
||||
|
||||
let mut min = encode_len(limit.min - 1);
|
||||
let mut codec = SSZSnappyOutboundCodec::<Spec>::new(protocol_id.clone(), 1_048_576);
|
||||
let mut codec = SSZSnappyOutboundCodec::<Spec>::new(
|
||||
protocol_id.clone(),
|
||||
1_048_576,
|
||||
fork_context.clone(),
|
||||
);
|
||||
assert_eq!(codec.decode(&mut min).unwrap_err(), RPCError::InvalidData);
|
||||
|
||||
// Request limits
|
||||
let limit = protocol_id.rpc_request_limits();
|
||||
let mut max = encode_len(limit.max + 1);
|
||||
let mut codec = SSZSnappyOutboundCodec::<Spec>::new(protocol_id.clone(), 1_048_576);
|
||||
let mut codec = SSZSnappyOutboundCodec::<Spec>::new(
|
||||
protocol_id.clone(),
|
||||
1_048_576,
|
||||
fork_context.clone(),
|
||||
);
|
||||
assert_eq!(codec.decode(&mut max).unwrap_err(), RPCError::InvalidData);
|
||||
|
||||
let mut min = encode_len(limit.min - 1);
|
||||
let mut codec = SSZSnappyOutboundCodec::<Spec>::new(protocol_id, 1_048_576);
|
||||
let mut codec = SSZSnappyOutboundCodec::<Spec>::new(protocol_id, 1_048_576, fork_context);
|
||||
assert_eq!(codec.decode(&mut min).unwrap_err(), RPCError::InvalidData);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_malicious_status_message() {
|
||||
// 10 byte snappy stream identifier
|
||||
let stream_identifier: &'static [u8] = b"\xFF\x06\x00\x00sNaPpY";
|
||||
|
||||
assert_eq!(stream_identifier.len(), 10);
|
||||
|
||||
// byte 0(0xFE) is padding chunk type identifier for snappy messages
|
||||
// byte 1,2,3 are chunk length (little endian)
|
||||
let malicious_padding: &'static [u8] = b"\xFE\x00\x00\x00";
|
||||
|
||||
// Status message is 84 bytes uncompressed. `max_compressed_len` is 32 + 84 + 84/6 = 130.
|
||||
let status_message_bytes = StatusMessage {
|
||||
fork_digest: [0; 4],
|
||||
finalized_root: Hash256::from_low_u64_be(0),
|
||||
finalized_epoch: Epoch::new(1),
|
||||
head_root: Hash256::from_low_u64_be(0),
|
||||
head_slot: Slot::new(1),
|
||||
}
|
||||
.as_ssz_bytes();
|
||||
|
||||
assert_eq!(status_message_bytes.len(), 84);
|
||||
assert_eq!(snap::raw::max_compress_len(status_message_bytes.len()), 130);
|
||||
|
||||
let mut uvi_codec: Uvi<usize> = Uvi::default();
|
||||
let mut dst = BytesMut::with_capacity(1024);
|
||||
|
||||
// Insert length-prefix
|
||||
uvi_codec
|
||||
.encode(status_message_bytes.len(), &mut dst)
|
||||
.unwrap();
|
||||
|
||||
// Insert snappy stream identifier
|
||||
dst.extend_from_slice(stream_identifier);
|
||||
|
||||
// Insert malicious padding of 80 bytes.
|
||||
for _ in 0..20 {
|
||||
dst.extend_from_slice(malicious_padding);
|
||||
}
|
||||
|
||||
// Insert payload (42 bytes compressed)
|
||||
let mut writer = FrameEncoder::new(Vec::new());
|
||||
writer.write_all(&status_message_bytes).unwrap();
|
||||
writer.flush().unwrap();
|
||||
assert_eq!(writer.get_ref().len(), 42);
|
||||
dst.extend_from_slice(writer.get_ref());
|
||||
|
||||
// 10 (for stream identifier) + 80 + 42 = 132 > `max_compressed_len`. Hence, decoding should fail with `InvalidData`.
|
||||
|
||||
let snappy_protocol_id =
|
||||
ProtocolId::new(Protocol::Status, Version::V1, Encoding::SSZSnappy);
|
||||
|
||||
let mut snappy_outbound_codec =
|
||||
SSZSnappyOutboundCodec::<Spec>::new(snappy_protocol_id, 1_048_576);
|
||||
|
||||
let snappy_decoded_message = snappy_outbound_codec.decode(&mut dst).unwrap_err();
|
||||
assert_eq!(snappy_decoded_message, RPCError::InvalidData);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@
|
||||
use super::methods::{
|
||||
GoodbyeReason, RPCCodedResponse, RPCResponseErrorCode, RequestId, ResponseTermination,
|
||||
};
|
||||
use super::outbound::OutboundRequestContainer;
|
||||
use super::protocol::{InboundRequest, Protocol, RPCError, RPCProtocol};
|
||||
use super::{RPCReceived, RPCSend};
|
||||
use crate::rpc::outbound::{OutboundFramed, OutboundRequest};
|
||||
@@ -23,12 +24,13 @@ use smallvec::SmallVec;
|
||||
use std::{
|
||||
collections::hash_map::Entry,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::time::{sleep_until, Instant as TInstant, Sleep};
|
||||
use tokio_util::time::{delay_queue, DelayQueue};
|
||||
use types::EthSpec;
|
||||
use types::{EthSpec, ForkContext};
|
||||
|
||||
/// The time (in seconds) before a substream that is awaiting a response from the user times out.
|
||||
pub const RESPONSE_TIMEOUT: u64 = 10;
|
||||
@@ -126,6 +128,9 @@ where
|
||||
/// This keeps track of the number of attempts.
|
||||
outbound_io_error_retries: u8,
|
||||
|
||||
/// Fork specific info.
|
||||
fork_context: Arc<ForkContext>,
|
||||
|
||||
/// Logger for handling RPC streams
|
||||
log: slog::Logger,
|
||||
}
|
||||
@@ -203,6 +208,7 @@ where
|
||||
{
|
||||
pub fn new(
|
||||
listen_protocol: SubstreamProtocol<RPCProtocol<TSpec>, ()>,
|
||||
fork_context: Arc<ForkContext>,
|
||||
log: &slog::Logger,
|
||||
) -> Self {
|
||||
RPCHandler {
|
||||
@@ -219,6 +225,7 @@ where
|
||||
state: HandlerState::Active,
|
||||
max_dial_negotiated: 8,
|
||||
outbound_io_error_retries: 0,
|
||||
fork_context,
|
||||
log: log.clone(),
|
||||
}
|
||||
}
|
||||
@@ -308,7 +315,7 @@ where
|
||||
type OutEvent = HandlerEvent<TSpec>;
|
||||
type Error = RPCError;
|
||||
type InboundProtocol = RPCProtocol<TSpec>;
|
||||
type OutboundProtocol = OutboundRequest<TSpec>;
|
||||
type OutboundProtocol = OutboundRequestContainer<TSpec>;
|
||||
type OutboundOpenInfo = (RequestId, OutboundRequest<TSpec>); // Keep track of the id and the request
|
||||
type InboundOpenInfo = ();
|
||||
|
||||
@@ -874,7 +881,14 @@ where
|
||||
let (id, req) = self.dial_queue.remove(0);
|
||||
self.dial_queue.shrink_to_fit();
|
||||
return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest {
|
||||
protocol: SubstreamProtocol::new(req.clone(), ()).map_info(|()| (id, req)),
|
||||
protocol: SubstreamProtocol::new(
|
||||
OutboundRequestContainer {
|
||||
req: req.clone(),
|
||||
fork_context: self.fork_context.clone(),
|
||||
},
|
||||
(),
|
||||
)
|
||||
.map_info(|()| (id, req)),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Available RPC methods types and ids.
|
||||
|
||||
use crate::types::EnrBitfield;
|
||||
use crate::types::{EnrAttestationBitfield, EnrSyncCommitteeBitfield};
|
||||
use regex::bytes::Regex;
|
||||
use serde::Serialize;
|
||||
use ssz_derive::{Decode, Encode};
|
||||
@@ -10,6 +10,7 @@ use ssz_types::{
|
||||
};
|
||||
use std::ops::Deref;
|
||||
use strum::AsStaticStr;
|
||||
use superstruct::superstruct;
|
||||
use types::{Epoch, EthSpec, Hash256, SignedBeaconBlock, Slot};
|
||||
|
||||
/// Maximum number of blocks in a single request.
|
||||
@@ -93,13 +94,23 @@ pub struct Ping {
|
||||
}
|
||||
|
||||
/// The METADATA response structure.
|
||||
#[derive(Encode, Decode, Clone, Debug, PartialEq, Serialize)]
|
||||
#[superstruct(
|
||||
variants(V1, V2),
|
||||
variant_attributes(
|
||||
derive(Encode, Decode, Clone, Debug, PartialEq, Serialize),
|
||||
serde(bound = "T: EthSpec", deny_unknown_fields),
|
||||
)
|
||||
)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Encode)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct MetaData<T: EthSpec> {
|
||||
/// A sequential counter indicating when data gets modified.
|
||||
pub seq_number: u64,
|
||||
/// The persistent subnet bitfield.
|
||||
pub attnets: EnrBitfield<T>,
|
||||
/// The persistent attestation subnet bitfield.
|
||||
pub attnets: EnrAttestationBitfield<T>,
|
||||
/// The persistent sync committee bitfield.
|
||||
#[superstruct(only(V2))]
|
||||
pub syncnets: EnrSyncCommitteeBitfield<T>,
|
||||
}
|
||||
|
||||
/// The reason given for a `Goodbye` message.
|
||||
@@ -360,7 +371,7 @@ impl<T: EthSpec> std::fmt::Display for RPCResponse<T> {
|
||||
write!(f, "BlocksByRoot: Block slot: {}", block.slot())
|
||||
}
|
||||
RPCResponse::Pong(ping) => write!(f, "Pong: {}", ping.data),
|
||||
RPCResponse::MetaData(metadata) => write!(f, "Metadata: {}", metadata.seq_number),
|
||||
RPCResponse::MetaData(metadata) => write!(f, "Metadata: {}", metadata.seq_number()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,13 @@ use libp2p::{Multiaddr, PeerId};
|
||||
use rate_limiter::{RPCRateLimiter as RateLimiter, RPCRateLimiterBuilder, RateLimitedErr};
|
||||
use slog::{crit, debug, o};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
use types::EthSpec;
|
||||
use types::{EthSpec, ForkContext};
|
||||
|
||||
pub(crate) use handler::HandlerErr;
|
||||
pub(crate) use methods::{MetaData, Ping, RPCCodedResponse, RPCResponse};
|
||||
pub(crate) use methods::{MetaData, MetaDataV1, MetaDataV2, Ping, RPCCodedResponse, RPCResponse};
|
||||
pub(crate) use protocol::{InboundRequest, RPCProtocol};
|
||||
|
||||
pub use handler::SubstreamId;
|
||||
@@ -101,12 +102,13 @@ pub struct RPC<TSpec: EthSpec> {
|
||||
limiter: RateLimiter,
|
||||
/// Queue of events to be processed.
|
||||
events: Vec<NetworkBehaviourAction<RPCSend<TSpec>, RPCMessage<TSpec>>>,
|
||||
fork_context: Arc<ForkContext>,
|
||||
/// Slog logger for RPC behaviour.
|
||||
log: slog::Logger,
|
||||
}
|
||||
|
||||
impl<TSpec: EthSpec> RPC<TSpec> {
|
||||
pub fn new(log: slog::Logger) -> Self {
|
||||
pub fn new(fork_context: Arc<ForkContext>, log: slog::Logger) -> Self {
|
||||
let log = log.new(o!("service" => "libp2p_rpc"));
|
||||
let limiter = RPCRateLimiterBuilder::new()
|
||||
.n_every(Protocol::MetaData, 2, Duration::from_secs(5))
|
||||
@@ -124,6 +126,7 @@ impl<TSpec: EthSpec> RPC<TSpec> {
|
||||
RPC {
|
||||
limiter,
|
||||
events: Vec::new(),
|
||||
fork_context,
|
||||
log,
|
||||
}
|
||||
}
|
||||
@@ -182,10 +185,12 @@ where
|
||||
RPCHandler::new(
|
||||
SubstreamProtocol::new(
|
||||
RPCProtocol {
|
||||
fork_context: self.fork_context.clone(),
|
||||
phantom: PhantomData,
|
||||
},
|
||||
(),
|
||||
),
|
||||
self.fork_context.clone(),
|
||||
&self.log,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,16 +14,23 @@ use futures::future::BoxFuture;
|
||||
use futures::prelude::{AsyncRead, AsyncWrite};
|
||||
use futures::{FutureExt, SinkExt};
|
||||
use libp2p::core::{OutboundUpgrade, UpgradeInfo};
|
||||
use std::sync::Arc;
|
||||
use tokio_util::{
|
||||
codec::Framed,
|
||||
compat::{Compat, FuturesAsyncReadCompatExt},
|
||||
};
|
||||
use types::EthSpec;
|
||||
use types::{EthSpec, ForkContext};
|
||||
/* Outbound request */
|
||||
|
||||
// Combines all the RPC requests into a single enum to implement `UpgradeInfo` and
|
||||
// `OutboundUpgrade`
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OutboundRequestContainer<TSpec: EthSpec> {
|
||||
pub req: OutboundRequest<TSpec>,
|
||||
pub fork_context: Arc<ForkContext>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum OutboundRequest<TSpec: EthSpec> {
|
||||
Status(StatusMessage),
|
||||
@@ -34,13 +41,13 @@ pub enum OutboundRequest<TSpec: EthSpec> {
|
||||
MetaData(PhantomData<TSpec>),
|
||||
}
|
||||
|
||||
impl<TSpec: EthSpec> UpgradeInfo for OutboundRequest<TSpec> {
|
||||
impl<TSpec: EthSpec> UpgradeInfo for OutboundRequestContainer<TSpec> {
|
||||
type Info = ProtocolId;
|
||||
type InfoIter = Vec<Self::Info>;
|
||||
|
||||
// add further protocols as we support more encodings/versions
|
||||
fn protocol_info(&self) -> Self::InfoIter {
|
||||
self.supported_protocols()
|
||||
self.req.supported_protocols()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,26 +66,23 @@ impl<TSpec: EthSpec> OutboundRequest<TSpec> {
|
||||
Version::V1,
|
||||
Encoding::SSZSnappy,
|
||||
)],
|
||||
OutboundRequest::BlocksByRange(_) => vec![ProtocolId::new(
|
||||
Protocol::BlocksByRange,
|
||||
Version::V1,
|
||||
Encoding::SSZSnappy,
|
||||
)],
|
||||
OutboundRequest::BlocksByRoot(_) => vec![ProtocolId::new(
|
||||
Protocol::BlocksByRoot,
|
||||
Version::V1,
|
||||
Encoding::SSZSnappy,
|
||||
)],
|
||||
OutboundRequest::BlocksByRange(_) => vec![
|
||||
ProtocolId::new(Protocol::BlocksByRange, Version::V2, Encoding::SSZSnappy),
|
||||
ProtocolId::new(Protocol::BlocksByRange, Version::V1, Encoding::SSZSnappy),
|
||||
],
|
||||
OutboundRequest::BlocksByRoot(_) => vec![
|
||||
ProtocolId::new(Protocol::BlocksByRoot, Version::V2, Encoding::SSZSnappy),
|
||||
ProtocolId::new(Protocol::BlocksByRoot, Version::V1, Encoding::SSZSnappy),
|
||||
],
|
||||
OutboundRequest::Ping(_) => vec![ProtocolId::new(
|
||||
Protocol::Ping,
|
||||
Version::V1,
|
||||
Encoding::SSZSnappy,
|
||||
)],
|
||||
OutboundRequest::MetaData(_) => vec![ProtocolId::new(
|
||||
Protocol::MetaData,
|
||||
Version::V1,
|
||||
Encoding::SSZSnappy,
|
||||
)],
|
||||
OutboundRequest::MetaData(_) => vec![
|
||||
ProtocolId::new(Protocol::MetaData, Version::V2, Encoding::SSZSnappy),
|
||||
ProtocolId::new(Protocol::MetaData, Version::V1, Encoding::SSZSnappy),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +134,7 @@ impl<TSpec: EthSpec> OutboundRequest<TSpec> {
|
||||
|
||||
pub type OutboundFramed<TSocket, TSpec> = Framed<Compat<TSocket>, OutboundCodec<TSpec>>;
|
||||
|
||||
impl<TSocket, TSpec> OutboundUpgrade<TSocket> for OutboundRequest<TSpec>
|
||||
impl<TSocket, TSpec> OutboundUpgrade<TSocket> for OutboundRequestContainer<TSpec>
|
||||
where
|
||||
TSpec: EthSpec + Send + 'static,
|
||||
TSocket: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
@@ -147,6 +151,7 @@ where
|
||||
let ssz_snappy_codec = BaseOutboundCodec::new(SSZSnappyOutboundCodec::new(
|
||||
protocol,
|
||||
usize::max_value(),
|
||||
self.fork_context.clone(),
|
||||
));
|
||||
OutboundCodec::SSZSnappy(ssz_snappy_codec)
|
||||
}
|
||||
@@ -155,7 +160,7 @@ where
|
||||
let mut socket = Framed::new(socket, codec);
|
||||
|
||||
async {
|
||||
socket.send(self).await?;
|
||||
socket.send(self.req).await?;
|
||||
socket.close().await?;
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use ssz::Encode;
|
||||
use ssz_types::VariableList;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use strum::{AsStaticRef, AsStaticStr};
|
||||
use tokio_io_timeout::TimeoutStream;
|
||||
@@ -19,19 +20,35 @@ use tokio_util::{
|
||||
codec::Framed,
|
||||
compat::{Compat, FuturesAsyncReadCompatExt},
|
||||
};
|
||||
use types::{BeaconBlock, EthSpec, Hash256, MainnetEthSpec, Signature, SignedBeaconBlock};
|
||||
use types::{
|
||||
BeaconBlock, BeaconBlockAltair, BeaconBlockBase, EthSpec, ForkContext, Hash256, MainnetEthSpec,
|
||||
Signature, SignedBeaconBlock,
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
// Note: Hardcoding the `EthSpec` type for `SignedBeaconBlock` as min/max values is
|
||||
// same across different `EthSpec` implementations.
|
||||
pub static ref SIGNED_BEACON_BLOCK_MIN: usize = SignedBeaconBlock::<MainnetEthSpec>::from_block(
|
||||
BeaconBlock::empty(&MainnetEthSpec::default_spec()),
|
||||
pub static ref SIGNED_BEACON_BLOCK_BASE_MIN: usize = SignedBeaconBlock::<MainnetEthSpec>::from_block(
|
||||
BeaconBlock::Base(BeaconBlockBase::<MainnetEthSpec>::empty(&MainnetEthSpec::default_spec())),
|
||||
Signature::empty(),
|
||||
)
|
||||
.as_ssz_bytes()
|
||||
.len();
|
||||
pub static ref SIGNED_BEACON_BLOCK_MAX: usize = SignedBeaconBlock::<MainnetEthSpec>::from_block(
|
||||
BeaconBlock::full(&MainnetEthSpec::default_spec()),
|
||||
pub static ref SIGNED_BEACON_BLOCK_BASE_MAX: usize = SignedBeaconBlock::<MainnetEthSpec>::from_block(
|
||||
BeaconBlock::Base(BeaconBlockBase::full(&MainnetEthSpec::default_spec())),
|
||||
Signature::empty(),
|
||||
)
|
||||
.as_ssz_bytes()
|
||||
.len();
|
||||
|
||||
pub static ref SIGNED_BEACON_BLOCK_ALTAIR_MIN: usize = SignedBeaconBlock::<MainnetEthSpec>::from_block(
|
||||
BeaconBlock::Altair(BeaconBlockAltair::<MainnetEthSpec>::empty(&MainnetEthSpec::default_spec())),
|
||||
Signature::empty(),
|
||||
)
|
||||
.as_ssz_bytes()
|
||||
.len();
|
||||
pub static ref SIGNED_BEACON_BLOCK_ALTAIR_MAX: usize = SignedBeaconBlock::<MainnetEthSpec>::from_block(
|
||||
BeaconBlock::Altair(BeaconBlockAltair::full(&MainnetEthSpec::default_spec())),
|
||||
Signature::empty(),
|
||||
)
|
||||
.as_ssz_bytes()
|
||||
@@ -95,6 +112,8 @@ pub enum Protocol {
|
||||
pub enum Version {
|
||||
/// Version 1 of RPC
|
||||
V1,
|
||||
/// Version 2 of RPC
|
||||
V2,
|
||||
}
|
||||
|
||||
/// RPC Encondings supported.
|
||||
@@ -130,6 +149,7 @@ impl std::fmt::Display for Version {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let repr = match self {
|
||||
Version::V1 => "1",
|
||||
Version::V2 => "2",
|
||||
};
|
||||
f.write_str(repr)
|
||||
}
|
||||
@@ -137,6 +157,7 @@ impl std::fmt::Display for Version {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RPCProtocol<TSpec: EthSpec> {
|
||||
pub fork_context: Arc<ForkContext>,
|
||||
pub phantom: PhantomData<TSpec>,
|
||||
}
|
||||
|
||||
@@ -149,9 +170,13 @@ impl<TSpec: EthSpec> UpgradeInfo for RPCProtocol<TSpec> {
|
||||
vec![
|
||||
ProtocolId::new(Protocol::Status, Version::V1, Encoding::SSZSnappy),
|
||||
ProtocolId::new(Protocol::Goodbye, Version::V1, Encoding::SSZSnappy),
|
||||
// V2 variants have higher preference then V1
|
||||
ProtocolId::new(Protocol::BlocksByRange, Version::V2, Encoding::SSZSnappy),
|
||||
ProtocolId::new(Protocol::BlocksByRange, Version::V1, Encoding::SSZSnappy),
|
||||
ProtocolId::new(Protocol::BlocksByRoot, Version::V2, Encoding::SSZSnappy),
|
||||
ProtocolId::new(Protocol::BlocksByRoot, Version::V1, Encoding::SSZSnappy),
|
||||
ProtocolId::new(Protocol::Ping, Version::V1, Encoding::SSZSnappy),
|
||||
ProtocolId::new(Protocol::MetaData, Version::V2, Encoding::SSZSnappy),
|
||||
ProtocolId::new(Protocol::MetaData, Version::V1, Encoding::SSZSnappy),
|
||||
]
|
||||
}
|
||||
@@ -226,22 +251,49 @@ impl ProtocolId {
|
||||
<StatusMessage as Encode>::ssz_fixed_len(),
|
||||
),
|
||||
Protocol::Goodbye => RpcLimits::new(0, 0), // Goodbye request has no response
|
||||
Protocol::BlocksByRange => {
|
||||
RpcLimits::new(*SIGNED_BEACON_BLOCK_MIN, *SIGNED_BEACON_BLOCK_MAX)
|
||||
}
|
||||
Protocol::BlocksByRoot => {
|
||||
RpcLimits::new(*SIGNED_BEACON_BLOCK_MIN, *SIGNED_BEACON_BLOCK_MAX)
|
||||
}
|
||||
Protocol::BlocksByRange => RpcLimits::new(
|
||||
std::cmp::min(
|
||||
*SIGNED_BEACON_BLOCK_ALTAIR_MIN,
|
||||
*SIGNED_BEACON_BLOCK_BASE_MIN,
|
||||
),
|
||||
std::cmp::max(
|
||||
*SIGNED_BEACON_BLOCK_ALTAIR_MAX,
|
||||
*SIGNED_BEACON_BLOCK_BASE_MAX,
|
||||
),
|
||||
),
|
||||
Protocol::BlocksByRoot => RpcLimits::new(
|
||||
std::cmp::min(
|
||||
*SIGNED_BEACON_BLOCK_ALTAIR_MIN,
|
||||
*SIGNED_BEACON_BLOCK_BASE_MIN,
|
||||
),
|
||||
std::cmp::max(
|
||||
*SIGNED_BEACON_BLOCK_ALTAIR_MAX,
|
||||
*SIGNED_BEACON_BLOCK_BASE_MAX,
|
||||
),
|
||||
),
|
||||
|
||||
Protocol::Ping => RpcLimits::new(
|
||||
<Ping as Encode>::ssz_fixed_len(),
|
||||
<Ping as Encode>::ssz_fixed_len(),
|
||||
),
|
||||
Protocol::MetaData => RpcLimits::new(
|
||||
<MetaData<T> as Encode>::ssz_fixed_len(),
|
||||
<MetaData<T> as Encode>::ssz_fixed_len(),
|
||||
<MetaDataV1<T> as Encode>::ssz_fixed_len(),
|
||||
<MetaDataV2<T> as Encode>::ssz_fixed_len(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the given `ProtocolId` should expect `context_bytes` in the
|
||||
/// beginning of the stream, else returns `false`.
|
||||
pub fn has_context_bytes(&self) -> bool {
|
||||
if self.version == Version::V2 {
|
||||
match self.message_name {
|
||||
Protocol::BlocksByRange | Protocol::BlocksByRoot => return true,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// An RPC protocol ID.
|
||||
@@ -292,8 +344,11 @@ where
|
||||
let socket = socket.compat();
|
||||
let codec = match protocol.encoding {
|
||||
Encoding::SSZSnappy => {
|
||||
let ssz_snappy_codec =
|
||||
BaseInboundCodec::new(SSZSnappyInboundCodec::new(protocol, MAX_RPC_SIZE));
|
||||
let ssz_snappy_codec = BaseInboundCodec::new(SSZSnappyInboundCodec::new(
|
||||
protocol,
|
||||
MAX_RPC_SIZE,
|
||||
self.fork_context.clone(),
|
||||
));
|
||||
InboundCodec::SSZSnappy(ssz_snappy_codec)
|
||||
}
|
||||
};
|
||||
@@ -359,26 +414,25 @@ impl<TSpec: EthSpec> InboundRequest<TSpec> {
|
||||
Version::V1,
|
||||
Encoding::SSZSnappy,
|
||||
)],
|
||||
InboundRequest::BlocksByRange(_) => vec![ProtocolId::new(
|
||||
Protocol::BlocksByRange,
|
||||
Version::V1,
|
||||
Encoding::SSZSnappy,
|
||||
)],
|
||||
InboundRequest::BlocksByRoot(_) => vec![ProtocolId::new(
|
||||
Protocol::BlocksByRoot,
|
||||
Version::V1,
|
||||
Encoding::SSZSnappy,
|
||||
)],
|
||||
InboundRequest::BlocksByRange(_) => vec![
|
||||
// V2 has higher preference when negotiating a stream
|
||||
ProtocolId::new(Protocol::BlocksByRange, Version::V2, Encoding::SSZSnappy),
|
||||
ProtocolId::new(Protocol::BlocksByRange, Version::V1, Encoding::SSZSnappy),
|
||||
],
|
||||
InboundRequest::BlocksByRoot(_) => vec![
|
||||
// V2 has higher preference when negotiating a stream
|
||||
ProtocolId::new(Protocol::BlocksByRoot, Version::V2, Encoding::SSZSnappy),
|
||||
ProtocolId::new(Protocol::BlocksByRoot, Version::V1, Encoding::SSZSnappy),
|
||||
],
|
||||
InboundRequest::Ping(_) => vec![ProtocolId::new(
|
||||
Protocol::Ping,
|
||||
Version::V1,
|
||||
Encoding::SSZSnappy,
|
||||
)],
|
||||
InboundRequest::MetaData(_) => vec![ProtocolId::new(
|
||||
Protocol::MetaData,
|
||||
Version::V1,
|
||||
Encoding::SSZSnappy,
|
||||
)],
|
||||
InboundRequest::MetaData(_) => vec![
|
||||
ProtocolId::new(Protocol::MetaData, Version::V2, Encoding::SSZSnappy),
|
||||
ProtocolId::new(Protocol::MetaData, Version::V1, Encoding::SSZSnappy),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,8 +478,6 @@ impl<TSpec: EthSpec> InboundRequest<TSpec> {
|
||||
}
|
||||
}
|
||||
|
||||
/* RPC Response type - used for outbound upgrades */
|
||||
|
||||
/// Error in RPC Encoding/Decoding.
|
||||
#[derive(Debug, Clone, PartialEq, AsStaticStr)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
|
||||
@@ -3,8 +3,10 @@ use crate::behaviour::{
|
||||
};
|
||||
use crate::discovery::enr;
|
||||
use crate::multiaddr::Protocol;
|
||||
use crate::rpc::{GoodbyeReason, MetaData, RPCResponseErrorCode, RequestId};
|
||||
use crate::types::{error, EnrBitfield, GossipKind};
|
||||
use crate::rpc::{
|
||||
GoodbyeReason, MetaData, MetaDataV1, MetaDataV2, RPCResponseErrorCode, RequestId,
|
||||
};
|
||||
use crate::types::{error, EnrAttestationBitfield, EnrSyncCommitteeBitfield, GossipKind};
|
||||
use crate::EnrExt;
|
||||
use crate::{NetworkConfig, NetworkGlobals, PeerAction, ReportSource};
|
||||
use futures::prelude::*;
|
||||
@@ -25,7 +27,7 @@ use std::io::prelude::*;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use types::{ChainSpec, EnrForkId, EthSpec};
|
||||
use types::{ChainSpec, EnrForkId, EthSpec, ForkContext};
|
||||
|
||||
use crate::peer_manager::{MIN_OUTBOUND_ONLY_FACTOR, PEER_EXCESS_FACTOR};
|
||||
|
||||
@@ -66,6 +68,7 @@ impl<TSpec: EthSpec> Service<TSpec> {
|
||||
config: &NetworkConfig,
|
||||
enr_fork_id: EnrForkId,
|
||||
log: &Logger,
|
||||
fork_context: Arc<ForkContext>,
|
||||
chain_spec: &ChainSpec,
|
||||
) -> error::Result<(Arc<NetworkGlobals<TSpec>>, Self)> {
|
||||
let log = log.new(o!("service"=> "libp2p"));
|
||||
@@ -112,9 +115,10 @@ impl<TSpec: EthSpec> Service<TSpec> {
|
||||
// Lighthouse network behaviour
|
||||
let behaviour = Behaviour::new(
|
||||
&local_keypair,
|
||||
config,
|
||||
config.clone(),
|
||||
network_globals.clone(),
|
||||
&log,
|
||||
fork_context,
|
||||
chain_spec,
|
||||
)
|
||||
.await?;
|
||||
@@ -547,37 +551,57 @@ fn load_or_build_metadata<E: EthSpec>(
|
||||
network_dir: &std::path::Path,
|
||||
log: &slog::Logger,
|
||||
) -> MetaData<E> {
|
||||
// Default metadata
|
||||
let mut meta_data = MetaData {
|
||||
// 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: EnrBitfield::<E>::default(),
|
||||
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() {
|
||||
match MetaData::<E>::from_ssz_bytes(&metadata_ssz) {
|
||||
// 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 {
|
||||
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(e) => {
|
||||
debug!(
|
||||
log,
|
||||
"Metadata from file could not be decoded";
|
||||
"error" => ?e,
|
||||
);
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
debug!(log, "Metadata sequence number"; "seq_num" => meta_data.seq_number);
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ mod topics;
|
||||
|
||||
use types::{BitVector, EthSpec};
|
||||
|
||||
#[allow(type_alias_bounds)]
|
||||
pub type EnrBitfield<T: EthSpec> = BitVector<T::SubnetBitfieldLength>;
|
||||
pub type EnrAttestationBitfield<T> = BitVector<<T as EthSpec>::SubnetBitfieldLength>;
|
||||
pub type EnrSyncCommitteeBitfield<T> = BitVector<<T as EthSpec>::SyncCommitteeSubnetCount>;
|
||||
|
||||
pub type Enr = discv5::enr::Enr<discv5::enr::CombinedKey>;
|
||||
|
||||
pub use globals::NetworkGlobals;
|
||||
pub use pubsub::{PubsubMessage, SnappyTransform};
|
||||
pub use subnet::SubnetDiscovery;
|
||||
pub use subnet::{Subnet, SubnetDiscovery};
|
||||
pub use sync_state::SyncState;
|
||||
pub use topics::{subnet_id_from_topic_hash, GossipEncoding, GossipKind, GossipTopic, CORE_TOPICS};
|
||||
pub use topics::{subnet_from_topic_hash, GossipEncoding, GossipKind, GossipTopic, CORE_TOPICS};
|
||||
|
||||
@@ -7,10 +7,10 @@ use snap::raw::{decompress_len, Decoder, Encoder};
|
||||
use ssz::{Decode, Encode};
|
||||
use std::boxed::Box;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use types::SubnetId;
|
||||
use types::{
|
||||
Attestation, AttesterSlashing, EthSpec, ProposerSlashing, SignedAggregateAndProof,
|
||||
SignedBeaconBlock, SignedBeaconBlockBase, SignedVoluntaryExit,
|
||||
Attestation, AttesterSlashing, EthSpec, ForkContext, ForkName, ProposerSlashing,
|
||||
SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase,
|
||||
SignedContributionAndProof, SignedVoluntaryExit, SubnetId, SyncCommitteeMessage, SyncSubnetId,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -27,6 +27,10 @@ pub enum PubsubMessage<T: EthSpec> {
|
||||
ProposerSlashing(Box<ProposerSlashing>),
|
||||
/// Gossipsub message providing notification of a new attester slashing.
|
||||
AttesterSlashing(Box<AttesterSlashing<T>>),
|
||||
/// Gossipsub message providing notification of partially aggregated sync committee signatures.
|
||||
SignedContributionAndProof(Box<SignedContributionAndProof<T>>),
|
||||
/// Gossipsub message providing notification of unaggregated sync committee signatures with its subnet id.
|
||||
SyncCommitteeMessage(Box<(SyncSubnetId, SyncCommitteeMessage)>),
|
||||
}
|
||||
|
||||
// Implements the `DataTransform` trait of gossipsub to employ snappy compression
|
||||
@@ -107,6 +111,8 @@ impl<T: EthSpec> PubsubMessage<T> {
|
||||
PubsubMessage::VoluntaryExit(_) => GossipKind::VoluntaryExit,
|
||||
PubsubMessage::ProposerSlashing(_) => GossipKind::ProposerSlashing,
|
||||
PubsubMessage::AttesterSlashing(_) => GossipKind::AttesterSlashing,
|
||||
PubsubMessage::SignedContributionAndProof(_) => GossipKind::SignedContributionAndProof,
|
||||
PubsubMessage::SyncCommitteeMessage(data) => GossipKind::SyncCommitteeMessage(data.0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +120,11 @@ impl<T: EthSpec> PubsubMessage<T> {
|
||||
/* Note: This is assuming we are not hashing topics. If we choose to hash topics, these will
|
||||
* need to be modified.
|
||||
*/
|
||||
pub fn decode(topic: &TopicHash, data: &[u8]) -> Result<Self, String> {
|
||||
pub fn decode(
|
||||
topic: &TopicHash,
|
||||
data: &[u8],
|
||||
fork_context: &ForkContext,
|
||||
) -> Result<Self, String> {
|
||||
match GossipTopic::decode(topic.as_str()) {
|
||||
Err(_) => Err(format!("Unknown gossipsub topic: {:?}", topic)),
|
||||
Ok(gossip_topic) => {
|
||||
@@ -141,11 +151,23 @@ impl<T: EthSpec> PubsubMessage<T> {
|
||||
))))
|
||||
}
|
||||
GossipKind::BeaconBlock => {
|
||||
// FIXME(altair): support Altair blocks
|
||||
let beacon_block = SignedBeaconBlock::Base(
|
||||
SignedBeaconBlockBase::from_ssz_bytes(data)
|
||||
.map_err(|e| format!("{:?}", e))?,
|
||||
);
|
||||
let beacon_block =
|
||||
match fork_context.from_context_bytes(gossip_topic.fork_digest) {
|
||||
Some(ForkName::Base) => SignedBeaconBlock::<T>::Base(
|
||||
SignedBeaconBlockBase::from_ssz_bytes(data)
|
||||
.map_err(|e| format!("{:?}", e))?,
|
||||
),
|
||||
Some(ForkName::Altair) => SignedBeaconBlock::<T>::Altair(
|
||||
SignedBeaconBlockAltair::from_ssz_bytes(data)
|
||||
.map_err(|e| format!("{:?}", e))?,
|
||||
),
|
||||
None => {
|
||||
return Err(format!(
|
||||
"Unknown gossipsub fork digest: {:?}",
|
||||
gossip_topic.fork_digest
|
||||
))
|
||||
}
|
||||
};
|
||||
Ok(PubsubMessage::BeaconBlock(Box::new(beacon_block)))
|
||||
}
|
||||
GossipKind::VoluntaryExit => {
|
||||
@@ -163,6 +185,21 @@ impl<T: EthSpec> PubsubMessage<T> {
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
Ok(PubsubMessage::AttesterSlashing(Box::new(attester_slashing)))
|
||||
}
|
||||
GossipKind::SignedContributionAndProof => {
|
||||
let sync_aggregate = SignedContributionAndProof::from_ssz_bytes(data)
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
Ok(PubsubMessage::SignedContributionAndProof(Box::new(
|
||||
sync_aggregate,
|
||||
)))
|
||||
}
|
||||
GossipKind::SyncCommitteeMessage(subnet_id) => {
|
||||
let sync_committee = SyncCommitteeMessage::from_ssz_bytes(data)
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
Ok(PubsubMessage::SyncCommitteeMessage(Box::new((
|
||||
*subnet_id,
|
||||
sync_committee,
|
||||
))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,6 +219,8 @@ impl<T: EthSpec> PubsubMessage<T> {
|
||||
PubsubMessage::ProposerSlashing(data) => data.as_ssz_bytes(),
|
||||
PubsubMessage::AttesterSlashing(data) => data.as_ssz_bytes(),
|
||||
PubsubMessage::Attestation(data) => data.1.as_ssz_bytes(),
|
||||
PubsubMessage::SignedContributionAndProof(data) => data.as_ssz_bytes(),
|
||||
PubsubMessage::SyncCommitteeMessage(data) => data.1.as_ssz_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,6 +249,12 @@ impl<T: EthSpec> std::fmt::Display for PubsubMessage<T> {
|
||||
PubsubMessage::VoluntaryExit(_data) => write!(f, "Voluntary Exit"),
|
||||
PubsubMessage::ProposerSlashing(_data) => write!(f, "Proposer Slashing"),
|
||||
PubsubMessage::AttesterSlashing(_data) => write!(f, "Attester Slashing"),
|
||||
PubsubMessage::SignedContributionAndProof(_) => {
|
||||
write!(f, "Signed Contribution and Proof")
|
||||
}
|
||||
PubsubMessage::SyncCommitteeMessage(data) => {
|
||||
write!(f, "Sync committee message: subnet_id: {}", *data.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,28 @@
|
||||
use serde::Serialize;
|
||||
use std::time::Instant;
|
||||
use types::SubnetId;
|
||||
use types::{SubnetId, SyncSubnetId};
|
||||
|
||||
/// Represents a subnet on an attestation or sync committee `SubnetId`.
|
||||
///
|
||||
/// Used for subscribing to the appropriate gossipsub subnets and mark
|
||||
/// appropriate metadata bitfields.
|
||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)]
|
||||
pub enum Subnet {
|
||||
/// Represents a gossipsub attestation subnet and the metadata `attnets` field.
|
||||
Attestation(SubnetId),
|
||||
/// Represents a gossipsub sync committee subnet and the metadata `syncnets` field.
|
||||
SyncCommittee(SyncSubnetId),
|
||||
}
|
||||
|
||||
/// A subnet to discover peers on along with the instant after which it's no longer useful.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubnetDiscovery {
|
||||
pub subnet_id: SubnetId,
|
||||
pub subnet: Subnet,
|
||||
pub min_ttl: Option<Instant>,
|
||||
}
|
||||
|
||||
impl PartialEq for SubnetDiscovery {
|
||||
fn eq(&self, other: &SubnetDiscovery) -> bool {
|
||||
self.subnet.eq(&other.subnet)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use libp2p::gossipsub::{IdentTopic as Topic, TopicHash};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use strum::AsRefStr;
|
||||
use types::SubnetId;
|
||||
use types::{SubnetId, SyncSubnetId};
|
||||
|
||||
use crate::Subnet;
|
||||
|
||||
/// The gossipsub topic names.
|
||||
// These constants form a topic name of the form /TOPIC_PREFIX/TOPIC/ENCODING_POSTFIX
|
||||
@@ -14,13 +16,16 @@ pub const BEACON_ATTESTATION_PREFIX: &str = "beacon_attestation_";
|
||||
pub const VOLUNTARY_EXIT_TOPIC: &str = "voluntary_exit";
|
||||
pub const PROPOSER_SLASHING_TOPIC: &str = "proposer_slashing";
|
||||
pub const ATTESTER_SLASHING_TOPIC: &str = "attester_slashing";
|
||||
pub const SIGNED_CONTRIBUTION_AND_PROOF_TOPIC: &str = "sync_committee_contribution_and_proof";
|
||||
pub const SYNC_COMMITTEE_PREFIX_TOPIC: &str = "sync_committee_";
|
||||
|
||||
pub const CORE_TOPICS: [GossipKind; 5] = [
|
||||
pub const CORE_TOPICS: [GossipKind; 6] = [
|
||||
GossipKind::BeaconBlock,
|
||||
GossipKind::BeaconAggregateAndProof,
|
||||
GossipKind::VoluntaryExit,
|
||||
GossipKind::ProposerSlashing,
|
||||
GossipKind::AttesterSlashing,
|
||||
GossipKind::SignedContributionAndProof,
|
||||
];
|
||||
|
||||
/// A gossipsub topic which encapsulates the type of messages that should be sent and received over
|
||||
@@ -30,7 +35,7 @@ pub struct GossipTopic {
|
||||
/// The encoding of the topic.
|
||||
encoding: GossipEncoding,
|
||||
/// The fork digest of the topic,
|
||||
fork_digest: [u8; 4],
|
||||
pub fork_digest: [u8; 4],
|
||||
/// The kind of topic.
|
||||
kind: GossipKind,
|
||||
}
|
||||
@@ -53,12 +58,20 @@ pub enum GossipKind {
|
||||
ProposerSlashing,
|
||||
/// Topic for publishing attester slashings.
|
||||
AttesterSlashing,
|
||||
/// Topic for publishing partially aggregated sync committee signatures.
|
||||
SignedContributionAndProof,
|
||||
/// Topic for publishing unaggregated sync committee signatures on a particular subnet.
|
||||
#[strum(serialize = "sync_committee")]
|
||||
SyncCommitteeMessage(SyncSubnetId),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GossipKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GossipKind::Attestation(subnet_id) => write!(f, "beacon_attestation_{}", **subnet_id),
|
||||
GossipKind::SyncCommitteeMessage(subnet_id) => {
|
||||
write!(f, "sync_committee_{}", **subnet_id)
|
||||
}
|
||||
x => f.write_str(x.as_ref()),
|
||||
}
|
||||
}
|
||||
@@ -124,11 +137,15 @@ impl GossipTopic {
|
||||
let kind = match topic_parts[3] {
|
||||
BEACON_BLOCK_TOPIC => GossipKind::BeaconBlock,
|
||||
BEACON_AGGREGATE_AND_PROOF_TOPIC => GossipKind::BeaconAggregateAndProof,
|
||||
SIGNED_CONTRIBUTION_AND_PROOF_TOPIC => GossipKind::SignedContributionAndProof,
|
||||
VOLUNTARY_EXIT_TOPIC => GossipKind::VoluntaryExit,
|
||||
PROPOSER_SLASHING_TOPIC => GossipKind::ProposerSlashing,
|
||||
ATTESTER_SLASHING_TOPIC => GossipKind::AttesterSlashing,
|
||||
topic => match committee_topic_index(topic) {
|
||||
Some(subnet_id) => GossipKind::Attestation(subnet_id),
|
||||
Some(subnet) => match subnet {
|
||||
Subnet::Attestation(s) => GossipKind::Attestation(s),
|
||||
Subnet::SyncCommittee(s) => GossipKind::SyncCommitteeMessage(s),
|
||||
},
|
||||
None => return Err(format!("Unknown topic: {}", topic)),
|
||||
},
|
||||
};
|
||||
@@ -163,6 +180,10 @@ impl From<GossipTopic> for String {
|
||||
GossipKind::ProposerSlashing => PROPOSER_SLASHING_TOPIC.into(),
|
||||
GossipKind::AttesterSlashing => ATTESTER_SLASHING_TOPIC.into(),
|
||||
GossipKind::Attestation(index) => format!("{}{}", BEACON_ATTESTATION_PREFIX, *index,),
|
||||
GossipKind::SignedContributionAndProof => SIGNED_CONTRIBUTION_AND_PROOF_TOPIC.into(),
|
||||
GossipKind::SyncCommitteeMessage(index) => {
|
||||
format!("{}{}", SYNC_COMMITTEE_PREFIX_TOPIC, *index)
|
||||
}
|
||||
};
|
||||
format!(
|
||||
"/{}/{}/{}/{}",
|
||||
@@ -174,32 +195,72 @@ impl From<GossipTopic> for String {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SubnetId> for GossipKind {
|
||||
fn from(subnet_id: SubnetId) -> Self {
|
||||
GossipKind::Attestation(subnet_id)
|
||||
impl std::fmt::Display for GossipTopic {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let encoding = match self.encoding {
|
||||
GossipEncoding::SSZSnappy => SSZ_SNAPPY_ENCODING_POSTFIX,
|
||||
};
|
||||
|
||||
let kind = match self.kind {
|
||||
GossipKind::BeaconBlock => BEACON_BLOCK_TOPIC.into(),
|
||||
GossipKind::BeaconAggregateAndProof => BEACON_AGGREGATE_AND_PROOF_TOPIC.into(),
|
||||
GossipKind::VoluntaryExit => VOLUNTARY_EXIT_TOPIC.into(),
|
||||
GossipKind::ProposerSlashing => PROPOSER_SLASHING_TOPIC.into(),
|
||||
GossipKind::AttesterSlashing => ATTESTER_SLASHING_TOPIC.into(),
|
||||
GossipKind::Attestation(index) => format!("{}{}", BEACON_ATTESTATION_PREFIX, *index,),
|
||||
GossipKind::SignedContributionAndProof => SIGNED_CONTRIBUTION_AND_PROOF_TOPIC.into(),
|
||||
GossipKind::SyncCommitteeMessage(index) => {
|
||||
format!("{}{}", SYNC_COMMITTEE_PREFIX_TOPIC, *index)
|
||||
}
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"/{}/{}/{}/{}",
|
||||
TOPIC_PREFIX,
|
||||
hex::encode(self.fork_digest),
|
||||
kind,
|
||||
encoding
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Subnet> for GossipKind {
|
||||
fn from(subnet_id: Subnet) -> Self {
|
||||
match subnet_id {
|
||||
Subnet::Attestation(s) => GossipKind::Attestation(s),
|
||||
Subnet::SyncCommittee(s) => GossipKind::SyncCommitteeMessage(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helper functions
|
||||
|
||||
/// Get subnet id from an attestation subnet topic hash.
|
||||
pub fn subnet_id_from_topic_hash(topic_hash: &TopicHash) -> Option<SubnetId> {
|
||||
pub fn subnet_from_topic_hash(topic_hash: &TopicHash) -> Option<Subnet> {
|
||||
let gossip_topic = GossipTopic::decode(topic_hash.as_str()).ok()?;
|
||||
if let GossipKind::Attestation(subnet_id) = gossip_topic.kind() {
|
||||
return Some(*subnet_id);
|
||||
match gossip_topic.kind() {
|
||||
GossipKind::Attestation(subnet_id) => Some(Subnet::Attestation(*subnet_id)),
|
||||
GossipKind::SyncCommitteeMessage(subnet_id) => Some(Subnet::SyncCommittee(*subnet_id)),
|
||||
_ => None,
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// Determines if a string is a committee topic.
|
||||
fn committee_topic_index(topic: &str) -> Option<SubnetId> {
|
||||
// Determines if a string is an attestation or sync committee topic.
|
||||
fn committee_topic_index(topic: &str) -> Option<Subnet> {
|
||||
if topic.starts_with(BEACON_ATTESTATION_PREFIX) {
|
||||
return Some(SubnetId::new(
|
||||
return Some(Subnet::Attestation(SubnetId::new(
|
||||
topic
|
||||
.trim_start_matches(BEACON_ATTESTATION_PREFIX)
|
||||
.parse::<u64>()
|
||||
.ok()?,
|
||||
));
|
||||
)));
|
||||
} else if topic.starts_with(SYNC_COMMITTEE_PREFIX_TOPIC) {
|
||||
return Some(Subnet::SyncCommittee(SyncSubnetId::new(
|
||||
topic
|
||||
.trim_start_matches(SYNC_COMMITTEE_PREFIX_TOPIC)
|
||||
.parse::<u64>()
|
||||
.ok()?,
|
||||
)));
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -222,7 +283,9 @@ mod tests {
|
||||
for kind in [
|
||||
BeaconBlock,
|
||||
BeaconAggregateAndProof,
|
||||
SignedContributionAndProof,
|
||||
Attestation(SubnetId::new(42)),
|
||||
SyncCommitteeMessage(SyncSubnetId::new(42)),
|
||||
VoluntaryExit,
|
||||
ProposerSlashing,
|
||||
AttesterSlashing,
|
||||
@@ -292,14 +355,20 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subnet_id_from_topic_hash() {
|
||||
fn test_subnet_from_topic_hash() {
|
||||
let topic_hash = TopicHash::from_raw("/eth2/e1925f3b/beacon_block/ssz_snappy");
|
||||
assert!(subnet_id_from_topic_hash(&topic_hash).is_none());
|
||||
assert!(subnet_from_topic_hash(&topic_hash).is_none());
|
||||
|
||||
let topic_hash = TopicHash::from_raw("/eth2/e1925f3b/beacon_attestation_42/ssz_snappy");
|
||||
assert_eq!(
|
||||
subnet_id_from_topic_hash(&topic_hash),
|
||||
Some(SubnetId::new(42))
|
||||
subnet_from_topic_hash(&topic_hash),
|
||||
Some(Subnet::Attestation(SubnetId::new(42)))
|
||||
);
|
||||
|
||||
let topic_hash = TopicHash::from_raw("/eth2/e1925f3b/sync_committee_42/ssz_snappy");
|
||||
assert_eq!(
|
||||
subnet_from_topic_hash(&topic_hash),
|
||||
Some(Subnet::SyncCommittee(SyncSubnetId::new(42)))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -314,6 +383,11 @@ mod tests {
|
||||
"beacon_attestation",
|
||||
Attestation(SubnetId::new(42)).as_ref()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"sync_committee",
|
||||
SyncCommitteeMessage(SyncSubnetId::new(42)).as_ref()
|
||||
);
|
||||
assert_eq!("voluntary_exit", VoluntaryExit.as_ref());
|
||||
assert_eq!("proposer_slashing", ProposerSlashing.as_ref());
|
||||
assert_eq!("attester_slashing", AttesterSlashing.as_ref());
|
||||
|
||||
Reference in New Issue
Block a user