// This file is part of Substrate.
// Copyright (C) 2017-2023 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//! `SyncingEngine` is the actor responsible for syncing Substrate chain
//! to tip and keep the blockchain up to date with network updates.
use crate::{
service::{self, chain_sync::ToServiceCommand},
ChainSync, ClientError, SyncingService,
};
use codec::{Decode, Encode};
use futures::{FutureExt, StreamExt};
use futures_timer::Delay;
use libp2p::PeerId;
use lru::LruCache;
use prometheus_endpoint::{
register, Gauge, GaugeVec, MetricSource, Opts, PrometheusError, Registry, SourcedGauge, U64,
};
use sc_client_api::{BlockBackend, HeaderBackend, ProofProvider};
use sc_consensus::import_queue::ImportQueueService;
use sc_network::{
config::{
NetworkConfiguration, NonDefaultSetConfig, ProtocolId, SyncMode as SyncOperationMode,
},
utils::LruHashSet,
NotificationsSink, ProtocolName,
};
use sc_network_common::{
role::Roles,
sync::{
message::{
generic::{BlockData, BlockResponse},
BlockAnnounce, BlockAnnouncesHandshake, BlockState,
},
warp::WarpSyncParams,
BadPeer, ChainSync as ChainSyncT, ExtendedPeerInfo, PollBlockAnnounceValidation, SyncEvent,
SyncMode,
},
};
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
use sp_blockchain::HeaderMetadata;
use sp_consensus::block_validation::BlockAnnounceValidator;
use sp_runtime::traits::{Block as BlockT, Header, NumberFor, Zero};
use std::{
collections::{HashMap, HashSet},
num::NonZeroUsize,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc,
},
task::Poll,
time::{Duration, Instant},
};
/// Interval at which we perform time based maintenance
const TICK_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(1100);
/// Maximum number of known block hashes to keep for a peer.
const MAX_KNOWN_BLOCKS: usize = 1024; // ~32kb per peer + LruHashSet overhead
/// If the block announces stream to peer has been inactive for two minutes meaning local node
/// has not sent or received block announcements to/from the peer, report the node for inactivity,
/// disconnect it and attempt to establish connection to some other peer.
const INACTIVITY_EVICT_THRESHOLD: Duration = Duration::from_secs(30);
mod rep {
use sc_peerset::ReputationChange as Rep;
/// Peer has different genesis.
pub const GENESIS_MISMATCH: Rep = Rep::new_fatal("Genesis mismatch");
/// Peer send us a block announcement that failed at validation.
pub const BAD_BLOCK_ANNOUNCEMENT: Rep = Rep::new(-(1 << 12), "Bad block announcement");
/// Block announce substream with the peer has been inactive too long
pub const INACTIVE_SUBSTREAM: Rep = Rep::new(-(1 << 10), "Inactive block announce substream");
}
struct Metrics {
peers: Gauge,
queued_blocks: Gauge,
fork_targets: Gauge,
justifications: GaugeVec,
}
impl Metrics {
fn register(r: &Registry, major_syncing: Arc) -> Result {
let _ = MajorSyncingGauge::register(r, major_syncing)?;
Ok(Self {
peers: {
let g = Gauge::new("substrate_sync_peers", "Number of peers we sync with")?;
register(g, r)?
},
queued_blocks: {
let g =
Gauge::new("substrate_sync_queued_blocks", "Number of blocks in import queue")?;
register(g, r)?
},
fork_targets: {
let g = Gauge::new("substrate_sync_fork_targets", "Number of fork sync targets")?;
register(g, r)?
},
justifications: {
let g = GaugeVec::new(
Opts::new(
"substrate_sync_extra_justifications",
"Number of extra justifications requests",
),
&["status"],
)?;
register(g, r)?
},
})
}
}
/// The "major syncing" metric.
#[derive(Clone)]
pub struct MajorSyncingGauge(Arc);
impl MajorSyncingGauge {
/// Registers the [`MajorSyncGauge`] metric whose value is
/// obtained from the given `AtomicBool`.
fn register(registry: &Registry, value: Arc) -> Result<(), PrometheusError> {
prometheus_endpoint::register(
SourcedGauge::new(
&Opts::new(
"substrate_sub_libp2p_is_major_syncing",
"Whether the node is performing a major sync or not.",
),
MajorSyncingGauge(value),
)?,
registry,
)?;
Ok(())
}
}
impl MetricSource for MajorSyncingGauge {
type N = u64;
fn collect(&self, mut set: impl FnMut(&[&str], Self::N)) {
set(&[], self.0.load(Ordering::Relaxed) as u64);
}
}
/// Peer information
#[derive(Debug)]
pub struct Peer {
pub info: ExtendedPeerInfo,
/// Holds a set of blocks known to this peer.
pub known_blocks: LruHashSet,
/// Notification sink.
sink: NotificationsSink,
/// Instant when the last notification was sent to peer.
last_notification_sent: Instant,
/// Instant when the last notification was received from peer.
last_notification_received: Instant,
}
pub struct SyncingEngine {
/// State machine that handles the list of in-progress requests. Only full node peers are
/// registered.
chain_sync: ChainSync,
/// Blockchain client.
client: Arc,
/// Number of peers we're connected to.
num_connected: Arc,
/// Are we actively catching up with the chain?
is_major_syncing: Arc,
/// Network service.
network_service: service::network::NetworkServiceHandle,
/// Channel for receiving service commands
service_rx: TracingUnboundedReceiver>,
/// Channel for receiving inbound connections from `Protocol`.
rx: sc_utils::mpsc::TracingUnboundedReceiver>,
/// Assigned roles.
roles: Roles,
/// Genesis hash.
genesis_hash: B::Hash,
/// Set of channels for other protocols that have subscribed to syncing events.
event_streams: Vec>,
/// Interval at which we call `tick`.
tick_timeout: Delay,
/// All connected peers. Contains both full and light node peers.
peers: HashMap>,
/// Evicted peers
evicted: HashSet,
/// List of nodes for which we perform additional logging because they are important for the
/// user.
important_peers: HashSet,
/// Actual list of connected no-slot nodes.
default_peers_set_no_slot_connected_peers: HashSet,
/// List of nodes that should never occupy peer slots.
default_peers_set_no_slot_peers: HashSet,
/// Value that was passed as part of the configuration. Used to cap the number of full
/// nodes.
default_peers_set_num_full: usize,
/// Number of slots to allocate to light nodes.
default_peers_set_num_light: usize,
/// A cache for the data that was associated to a block announcement.
block_announce_data_cache: LruCache>,
/// The `PeerId`'s of all boot nodes.
boot_node_ids: HashSet,
/// Protocol name used for block announcements
block_announce_protocol_name: ProtocolName,
/// Prometheus metrics.
metrics: Option,
}
impl SyncingEngine
where
B: BlockT,
Client: HeaderBackend
+ BlockBackend
+ HeaderMetadata
+ ProofProvider
+ Send
+ Sync
+ 'static,
{
pub fn new(
roles: Roles,
client: Arc,
metrics_registry: Option<&Registry>,
network_config: &NetworkConfiguration,
protocol_id: ProtocolId,
fork_id: &Option,
block_announce_validator: Box + Send>,
warp_sync_params: Option>,
network_service: service::network::NetworkServiceHandle,
import_queue: Box>,
block_request_protocol_name: ProtocolName,
state_request_protocol_name: ProtocolName,
warp_sync_protocol_name: Option,
rx: sc_utils::mpsc::TracingUnboundedReceiver>,
) -> Result<(Self, SyncingService, NonDefaultSetConfig), ClientError> {
let mode = match network_config.sync_mode {
SyncOperationMode::Full => SyncMode::Full,
SyncOperationMode::Fast { skip_proofs, storage_chain_mode } =>
SyncMode::LightState { skip_proofs, storage_chain_mode },
SyncOperationMode::Warp => SyncMode::Warp,
};
let max_parallel_downloads = network_config.max_parallel_downloads;
let max_blocks_per_request = if network_config.max_blocks_per_request >
crate::MAX_BLOCKS_IN_RESPONSE as u32
{
log::info!(target: "sync", "clamping maximum blocks per request to {}", crate::MAX_BLOCKS_IN_RESPONSE);
crate::MAX_BLOCKS_IN_RESPONSE as u32
} else {
network_config.max_blocks_per_request
};
let cache_capacity = NonZeroUsize::new(
(network_config.default_peers_set.in_peers as usize +
network_config.default_peers_set.out_peers as usize)
.max(1),
)
.expect("cache capacity is not zero");
let important_peers = {
let mut imp_p = HashSet::new();
for reserved in &network_config.default_peers_set.reserved_nodes {
imp_p.insert(reserved.peer_id);
}
for reserved in network_config
.extra_sets
.iter()
.flat_map(|s| s.set_config.reserved_nodes.iter())
{
imp_p.insert(reserved.peer_id);
}
imp_p.shrink_to_fit();
imp_p
};
let boot_node_ids = {
let mut list = HashSet::new();
for node in &network_config.boot_nodes {
list.insert(node.peer_id);
}
list.shrink_to_fit();
list
};
let default_peers_set_no_slot_peers = {
let mut no_slot_p: HashSet = network_config
.default_peers_set
.reserved_nodes
.iter()
.map(|reserved| reserved.peer_id)
.collect();
no_slot_p.shrink_to_fit();
no_slot_p
};
let default_peers_set_num_full = network_config.default_peers_set_num_full as usize;
let default_peers_set_num_light = {
let total = network_config.default_peers_set.out_peers +
network_config.default_peers_set.in_peers;
total.saturating_sub(network_config.default_peers_set_num_full) as usize
};
let (chain_sync, block_announce_config) = ChainSync::new(
mode,
client.clone(),
protocol_id,
fork_id,
roles,
block_announce_validator,
max_parallel_downloads,
max_blocks_per_request,
warp_sync_params,
metrics_registry,
network_service.clone(),
import_queue,
block_request_protocol_name,
state_request_protocol_name,
warp_sync_protocol_name,
)?;
let block_announce_protocol_name = block_announce_config.notifications_protocol.clone();
let (tx, service_rx) = tracing_unbounded("mpsc_chain_sync", 100_000);
let num_connected = Arc::new(AtomicUsize::new(0));
let is_major_syncing = Arc::new(AtomicBool::new(false));
let genesis_hash = client
.block_hash(0u32.into())
.ok()
.flatten()
.expect("Genesis block exists; qed");
Ok((
Self {
roles,
client,
chain_sync,
network_service,
peers: HashMap::new(),
evicted: HashSet::new(),
block_announce_data_cache: LruCache::new(cache_capacity),
block_announce_protocol_name,
num_connected: num_connected.clone(),
is_major_syncing: is_major_syncing.clone(),
service_rx,
rx,
genesis_hash,
important_peers,
default_peers_set_no_slot_connected_peers: HashSet::new(),
boot_node_ids,
default_peers_set_no_slot_peers,
default_peers_set_num_full,
default_peers_set_num_light,
event_streams: Vec::new(),
tick_timeout: Delay::new(TICK_TIMEOUT),
metrics: if let Some(r) = metrics_registry {
match Metrics::register(r, is_major_syncing.clone()) {
Ok(metrics) => Some(metrics),
Err(err) => {
log::error!(target: "sync", "Failed to register metrics {err:?}");
None
},
}
} else {
None
},
},
SyncingService::new(tx, num_connected, is_major_syncing),
block_announce_config,
))
}
/// Report Prometheus metrics.
pub fn report_metrics(&self) {
if let Some(metrics) = &self.metrics {
let n = u64::try_from(self.peers.len()).unwrap_or(std::u64::MAX);
metrics.peers.set(n);
let m = self.chain_sync.metrics();
metrics.fork_targets.set(m.fork_targets.into());
metrics.queued_blocks.set(m.queued_blocks.into());
metrics
.justifications
.with_label_values(&["pending"])
.set(m.justifications.pending_requests.into());
metrics
.justifications
.with_label_values(&["active"])
.set(m.justifications.active_requests.into());
metrics
.justifications
.with_label_values(&["failed"])
.set(m.justifications.failed_requests.into());
metrics
.justifications
.with_label_values(&["importing"])
.set(m.justifications.importing_requests.into());
}
}
fn update_peer_info(&mut self, who: &PeerId) {
if let Some(info) = self.chain_sync.peer_info(who) {
if let Some(ref mut peer) = self.peers.get_mut(who) {
peer.info.best_hash = info.best_hash;
peer.info.best_number = info.best_number;
}
}
}
/// Process the result of the block announce validation.
pub fn process_block_announce_validation_result(
&mut self,
validation_result: PollBlockAnnounceValidation,
) {
let (header, _is_best, who) = match validation_result {
PollBlockAnnounceValidation::Skip => return,
PollBlockAnnounceValidation::Nothing { is_best: _, who, announce } => {
self.update_peer_info(&who);
if let Some(data) = announce.data {
if !data.is_empty() {
self.block_announce_data_cache.put(announce.header.hash(), data);
}
}
return
},
PollBlockAnnounceValidation::ImportHeader { announce, is_best, who } => {
self.update_peer_info(&who);
if let Some(data) = announce.data {
if !data.is_empty() {
self.block_announce_data_cache.put(announce.header.hash(), data);
}
}
(announce.header, is_best, who)
},
PollBlockAnnounceValidation::Failure { who, disconnect } => {
if disconnect {
self.network_service
.disconnect_peer(who, self.block_announce_protocol_name.clone());
}
self.network_service.report_peer(who, rep::BAD_BLOCK_ANNOUNCEMENT);
return
},
};
// to import header from announced block let's construct response to request that normally
// would have been sent over network (but it is not in our case)
let blocks_to_import = self.chain_sync.on_block_data(
&who,
None,
BlockResponse {
id: 0,
blocks: vec![BlockData {
hash: header.hash(),
header: Some(header),
body: None,
indexed_body: None,
receipt: None,
message_queue: None,
justification: None,
justifications: None,
}],
},
);
self.chain_sync.process_block_response_data(blocks_to_import);
}
/// Push a block announce validation.
///
/// It is required that [`ChainSync::poll_block_announce_validation`] is
/// called later to check for finished validations. The result of the validation
/// needs to be passed to [`SyncingEngine::process_block_announce_validation_result`]
/// to finish the processing.
///
/// # Note
///
/// This will internally create a future, but this future will not be registered
/// in the task before being polled once. So, it is required to call
/// [`ChainSync::poll_block_announce_validation`] to ensure that the future is
/// registered properly and will wake up the task when being ready.
pub fn push_block_announce_validation(
&mut self,
who: PeerId,
announce: BlockAnnounce,
) {
let hash = announce.header.hash();
let peer = match self.peers.get_mut(&who) {
Some(p) => p,
None => {
log::error!(target: "sync", "Received block announce from disconnected peer {}", who);
debug_assert!(false);
return
},
};
peer.known_blocks.insert(hash);
peer.last_notification_received = Instant::now();
if peer.info.roles.is_full() {
let is_best = match announce.state.unwrap_or(BlockState::Best) {
BlockState::Best => true,
BlockState::Normal => false,
};
self.chain_sync.push_block_announce_validation(who, hash, announce, is_best);
}
}
/// Make sure an important block is propagated to peers.
///
/// In chain-based consensus, we often need to make sure non-best forks are
/// at least temporarily synced.
pub fn announce_block(&mut self, hash: B::Hash, data: Option>) {
let header = match self.client.header(hash) {
Ok(Some(header)) => header,
Ok(None) => {
log::warn!(target: "sync", "Trying to announce unknown block: {}", hash);
return
},
Err(e) => {
log::warn!(target: "sync", "Error reading block header {}: {}", hash, e);
return
},
};
// don't announce genesis block since it will be ignored
if header.number().is_zero() {
return
}
let is_best = self.client.info().best_hash == hash;
log::debug!(target: "sync", "Reannouncing block {:?} is_best: {}", hash, is_best);
let data = data
.or_else(|| self.block_announce_data_cache.get(&hash).cloned())
.unwrap_or_default();
for (who, ref mut peer) in self.peers.iter_mut() {
let inserted = peer.known_blocks.insert(hash);
if inserted {
log::trace!(target: "sync", "Announcing block {:?} to {}", hash, who);
let message = BlockAnnounce {
header: header.clone(),
state: if is_best { Some(BlockState::Best) } else { Some(BlockState::Normal) },
data: Some(data.clone()),
};
peer.last_notification_sent = Instant::now();
peer.sink.send_sync_notification(message.encode());
}
}
}
/// Inform sync about new best imported block.
pub fn new_best_block_imported(&mut self, hash: B::Hash, number: NumberFor) {
log::debug!(target: "sync", "New best block imported {:?}/#{}", hash, number);
self.chain_sync.update_chain_info(&hash, number);
self.network_service.set_notification_handshake(
self.block_announce_protocol_name.clone(),
BlockAnnouncesHandshake::::build(self.roles, number, hash, self.genesis_hash)
.encode(),
)
}
pub async fn run(mut self) {
loop {
futures::future::poll_fn(|cx| self.poll(cx)).await;
}
}
pub fn poll(&mut self, cx: &mut std::task::Context) -> Poll<()> {
self.num_connected.store(self.peers.len(), Ordering::Relaxed);
self.is_major_syncing
.store(self.chain_sync.status().state.is_major_syncing(), Ordering::Relaxed);
while let Poll::Ready(()) = self.tick_timeout.poll_unpin(cx) {
self.report_metrics();
// go over all connected peers and check if any of them have been idle for a while. Idle
// in this case means that we haven't sent or received block announcements to/from this
// peer. If that is the case, because of #5685, it could be that the block announces
// substream is not actually open and and this peer is just wasting a slot and is should
// be replaced with some other node that is willing to send us block announcements.
for (id, peer) in self.peers.iter() {
// because of a delay between disconnecting a peer in `SyncingEngine` and getting
// the response back from `Protocol`, a peer might be reported and disconnect
// multiple times. To prevent this from happening (until the underlying issue is
// fixed), keep track of evicted peers and report and disconnect them only once.
if self.evicted.contains(id) {
continue
}
let last_received_late =
peer.last_notification_received.elapsed() > INACTIVITY_EVICT_THRESHOLD;
let last_sent_late =
peer.last_notification_sent.elapsed() > INACTIVITY_EVICT_THRESHOLD;
if last_received_late && last_sent_late {
log::debug!(target: "sync", "evict peer {id} since it has been idling for too long");
self.network_service.report_peer(*id, rep::INACTIVE_SUBSTREAM);
self.network_service
.disconnect_peer(*id, self.block_announce_protocol_name.clone());
self.evicted.insert(*id);
}
}
self.tick_timeout.reset(TICK_TIMEOUT);
}
while let Poll::Ready(Some(event)) = self.service_rx.poll_next_unpin(cx) {
match event {
ToServiceCommand::SetSyncForkRequest(peers, hash, number) => {
self.chain_sync.set_sync_fork_request(peers, &hash, number);
},
ToServiceCommand::EventStream(tx) => self.event_streams.push(tx),
ToServiceCommand::RequestJustification(hash, number) =>
self.chain_sync.request_justification(&hash, number),
ToServiceCommand::ClearJustificationRequests =>
self.chain_sync.clear_justification_requests(),
ToServiceCommand::BlocksProcessed(imported, count, results) => {
for result in self.chain_sync.on_blocks_processed(imported, count, results) {
match result {
Ok((id, req)) => self.chain_sync.send_block_request(id, req),
Err(BadPeer(id, repu)) => {
self.network_service
.disconnect_peer(id, self.block_announce_protocol_name.clone());
self.network_service.report_peer(id, repu)
},
}
}
},
ToServiceCommand::JustificationImported(peer, hash, number, success) => {
self.chain_sync.on_justification_import(hash, number, success);
if !success {
log::info!(target: "sync", "💔 Invalid justification provided by {} for #{}", peer, hash);
self.network_service
.disconnect_peer(peer, self.block_announce_protocol_name.clone());
self.network_service.report_peer(
peer,
sc_peerset::ReputationChange::new_fatal("Invalid justification"),
);
}
},
ToServiceCommand::AnnounceBlock(hash, data) => self.announce_block(hash, data),
ToServiceCommand::NewBestBlockImported(hash, number) =>
self.new_best_block_imported(hash, number),
ToServiceCommand::Status(tx) => {
let mut status = self.chain_sync.status();
status.num_connected_peers = self.peers.len() as u32;
let _ = tx.send(status);
},
ToServiceCommand::NumActivePeers(tx) => {
let _ = tx.send(self.chain_sync.num_active_peers());
},
ToServiceCommand::SyncState(tx) => {
let _ = tx.send(self.chain_sync.status());
},
ToServiceCommand::BestSeenBlock(tx) => {
let _ = tx.send(self.chain_sync.status().best_seen_block);
},
ToServiceCommand::NumSyncPeers(tx) => {
let _ = tx.send(self.chain_sync.status().num_peers);
},
ToServiceCommand::NumQueuedBlocks(tx) => {
let _ = tx.send(self.chain_sync.status().queued_blocks);
},
ToServiceCommand::NumDownloadedBlocks(tx) => {
let _ = tx.send(self.chain_sync.num_downloaded_blocks());
},
ToServiceCommand::NumSyncRequests(tx) => {
let _ = tx.send(self.chain_sync.num_sync_requests());
},
ToServiceCommand::PeersInfo(tx) => {
let peers_info =
self.peers.iter().map(|(id, peer)| (*id, peer.info.clone())).collect();
let _ = tx.send(peers_info);
},
ToServiceCommand::OnBlockFinalized(hash, header) =>
self.chain_sync.on_block_finalized(&hash, *header.number()),
}
}
while let Poll::Ready(Some(event)) = self.rx.poll_next_unpin(cx) {
match event {
sc_network::SyncEvent::NotificationStreamOpened {
remote,
received_handshake,
sink,
tx,
} => match self.on_sync_peer_connected(remote, &received_handshake, sink) {
Ok(()) => {
let _ = tx.send(true);
},
Err(()) => {
log::debug!(
target: "sync",
"Failed to register peer {remote:?}: {received_handshake:?}",
);
let _ = tx.send(false);
},
},
sc_network::SyncEvent::NotificationStreamClosed { remote } => {
self.evicted.remove(&remote);
if self.on_sync_peer_disconnected(remote).is_err() {
log::trace!(
target: "sync",
"Disconnected peer which had earlier been refused by on_sync_peer_connected {}",
remote
);
}
},
sc_network::SyncEvent::NotificationsReceived { remote, messages } => {
for message in messages {
if self.peers.contains_key(&remote) {
if let Ok(announce) = BlockAnnounce::decode(&mut message.as_ref()) {
self.push_block_announce_validation(remote, announce);
// Make sure that the newly added block announce validation future
// was polled once to be registered in the task.
if let Poll::Ready(res) =
self.chain_sync.poll_block_announce_validation(cx)
{
self.process_block_announce_validation_result(res)
}
} else {
log::warn!(target: "sub-libp2p", "Failed to decode block announce");
}
} else {
log::trace!(
target: "sync",
"Received sync for peer earlier refused by sync layer: {}",
remote
);
}
}
},
sc_network::SyncEvent::NotificationSinkReplaced { remote, sink } => {
if let Some(peer) = self.peers.get_mut(&remote) {
peer.sink = sink;
}
},
}
}
// poll `ChainSync` last because of a block announcement was received through the
// event stream between `SyncingEngine` and `Protocol` and the validation finished
// right after it as queued, the resulting block request (if any) can be sent right away.
while let Poll::Ready(result) = self.chain_sync.poll(cx) {
self.process_block_announce_validation_result(result);
}
Poll::Pending
}
/// Called by peer when it is disconnecting.
///
/// Returns a result if the handshake of this peer was indeed accepted.
pub fn on_sync_peer_disconnected(&mut self, peer: PeerId) -> Result<(), ()> {
if self.peers.remove(&peer).is_some() {
if self.important_peers.contains(&peer) {
log::warn!(target: "sync", "Reserved peer {} disconnected", peer);
} else {
log::debug!(target: "sync", "{} disconnected", peer);
}
self.chain_sync.peer_disconnected(&peer);
self.default_peers_set_no_slot_connected_peers.remove(&peer);
self.event_streams
.retain(|stream| stream.unbounded_send(SyncEvent::PeerDisconnected(peer)).is_ok());
Ok(())
} else {
Err(())
}
}
/// Called on the first connection between two peers on the default set, after their exchange
/// of handshake.
///
/// Returns `Ok` if the handshake is accepted and the peer added to the list of peers we sync
/// from.
pub fn on_sync_peer_connected(
&mut self,
who: PeerId,
status: &BlockAnnouncesHandshake,
sink: NotificationsSink,
) -> Result<(), ()> {
log::trace!(target: "sync", "New peer {} {:?}", who, status);
if self.peers.contains_key(&who) {
log::error!(target: "sync", "Called on_sync_peer_connected with already connected peer {}", who);
debug_assert!(false);
return Err(())
}
if status.genesis_hash != self.genesis_hash {
self.network_service.report_peer(who, rep::GENESIS_MISMATCH);
if self.important_peers.contains(&who) {
log::error!(
target: "sync",
"Reserved peer id `{}` is on a different chain (our genesis: {} theirs: {})",
who,
self.genesis_hash,
status.genesis_hash,
);
} else if self.boot_node_ids.contains(&who) {
log::error!(
target: "sync",
"Bootnode with peer id `{}` is on a different chain (our genesis: {} theirs: {})",
who,
self.genesis_hash,
status.genesis_hash,
);
} else {
log::debug!(
target: "sync",
"Peer is on different chain (our genesis: {} theirs: {})",
self.genesis_hash, status.genesis_hash
);
}
return Err(())
}
let no_slot_peer = self.default_peers_set_no_slot_peers.contains(&who);
let this_peer_reserved_slot: usize = if no_slot_peer { 1 } else { 0 };
if status.roles.is_full() &&
self.chain_sync.num_peers() >=
self.default_peers_set_num_full +
self.default_peers_set_no_slot_connected_peers.len() +
this_peer_reserved_slot
{
log::debug!(target: "sync", "Too many full nodes, rejecting {}", who);
return Err(())
}
if status.roles.is_light() &&
(self.peers.len() - self.chain_sync.num_peers()) >= self.default_peers_set_num_light
{
// Make sure that not all slots are occupied by light clients.
log::debug!(target: "sync", "Too many light nodes, rejecting {}", who);
return Err(())
}
let peer = Peer {
info: ExtendedPeerInfo {
roles: status.roles,
best_hash: status.best_hash,
best_number: status.best_number,
},
known_blocks: LruHashSet::new(
NonZeroUsize::new(MAX_KNOWN_BLOCKS).expect("Constant is nonzero"),
),
sink,
last_notification_sent: Instant::now(),
last_notification_received: Instant::now(),
};
let req = if peer.info.roles.is_full() {
match self.chain_sync.new_peer(who, peer.info.best_hash, peer.info.best_number) {
Ok(req) => req,
Err(BadPeer(id, repu)) => {
self.network_service.report_peer(id, repu);
return Err(())
},
}
} else {
None
};
log::debug!(target: "sync", "Connected {}", who);
self.peers.insert(who, peer);
if no_slot_peer {
self.default_peers_set_no_slot_connected_peers.insert(who);
}
if let Some(req) = req {
self.chain_sync.send_block_request(who, req);
}
self.event_streams
.retain(|stream| stream.unbounded_send(SyncEvent::PeerConnected(who)).is_ok());
Ok(())
}
}