// Copyright 2017-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see .
//! Contains the state of the chain synchronization process
//!
//! At any given point in time, a running node tries as much as possible to be at the head of the
//! chain. This module handles the logic of which blocks to request from remotes, and processing
//! responses. It yields blocks to check and potentially move to the database.
//!
//! # Usage
//!
//! The `ChainSync` struct maintains the state of the block requests. Whenever something happens on
//! the network, or whenever a block has been successfully verified, call the appropriate method in
//! order to update it. You must also regularly call `tick()`.
//!
//! To each of these methods, you must pass a `Context` object that the `ChainSync` will use to
//! send its new outgoing requests.
//!
use std::cmp::max;
use std::ops::Range;
use std::collections::{HashMap, VecDeque};
use log::{debug, trace, warn, info, error};
use crate::protocol::PeerInfo as ProtocolPeerInfo;
use network_libp2p::PeerId;
use client::{BlockStatus, ClientInfo};
use consensus::{BlockOrigin, import_queue::{IncomingBlock, SharedFinalityProofRequestBuilder}};
use client::error::Error as ClientError;
use crate::blocks::BlockCollection;
use crate::sync::extra_requests::ExtraRequestsAggregator;
use runtime_primitives::traits::{
Block as BlockT, Header as HeaderT, NumberFor, Zero, One,
CheckedSub, SaturatedConversion
};
use runtime_primitives::{Justification, generic::BlockId};
use crate::message;
use crate::config::Roles;
use std::collections::HashSet;
mod extra_requests;
// Maximum blocks to request in a single packet.
const MAX_BLOCKS_TO_REQUEST: usize = 128;
// Maximum blocks to store in the import queue.
const MAX_IMPORTING_BLOCKS: usize = 2048;
// Number of blocks in the queue that prevents ancestry search.
const MAJOR_SYNC_BLOCKS: usize = 5;
// Number of recently announced blocks to track for each peer.
const ANNOUNCE_HISTORY_SIZE: usize = 64;
// Max number of blocks to download for unknown forks.
const MAX_UNKNOWN_FORK_DOWNLOAD_LEN: u32 = 32;
/// Reputation change when a peer sent us a status message that led to a database read error.
const BLOCKCHAIN_STATUS_READ_ERROR_REPUTATION_CHANGE: i32 = -(1 << 16);
/// Reputation change when a peer failed to answer our legitimate ancestry block search.
const ANCESTRY_BLOCK_ERROR_REPUTATION_CHANGE: i32 = -(1 << 9);
/// Reputation change when a peer sent us a status message with a different genesis than us.
const GENESIS_MISMATCH_REPUTATION_CHANGE: i32 = i32::min_value() + 1;
/// Context for a network-specific handler.
pub trait Context {
/// Get a reference to the client.
fn client(&self) -> &dyn crate::chain::Client;
/// Adjusts the reputation of the peer. Use this to point out that a peer has been malign or
/// irresponsible or appeared lazy.
fn report_peer(&mut self, who: PeerId, reputation: i32);
/// Force disconnecting from a peer. Use this when a peer misbehaved.
fn disconnect_peer(&mut self, who: PeerId);
/// Request a finality proof from a peer.
fn send_finality_proof_request(&mut self, who: PeerId, request: message::FinalityProofRequest);
/// Request a block from a peer.
fn send_block_request(&mut self, who: PeerId, request: message::BlockRequest);
}
#[derive(Debug)]
pub(crate) struct PeerSync {
pub common_number: NumberFor,
pub best_hash: B::Hash,
pub best_number: NumberFor,
pub state: PeerSyncState,
pub recently_announced: VecDeque,
}
#[derive(Debug)]
/// Peer sync status.
pub(crate) struct PeerInfo {
/// Their best block hash.
pub best_hash: B::Hash,
/// Their best block number.
pub best_number: NumberFor,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub(crate) enum AncestorSearchState {
/// Use exponential backoff to find an ancestor, then switch to binary search.
/// We keep track of the exponent.
ExponentialBackoff(NumberFor),
/// Using binary search to find the best ancestor.
/// We keep track of left and right bounds.
BinarySearch(NumberFor, NumberFor),
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub(crate) enum PeerSyncState {
AncestorSearch(NumberFor, AncestorSearchState),
Available,
DownloadingNew(NumberFor),
DownloadingStale(B::Hash),
DownloadingJustification(B::Hash),
DownloadingFinalityProof(B::Hash),
}
/// Relay chain sync strategy.
pub struct ChainSync {
_genesis_hash: B::Hash,
peers: HashMap>,
blocks: BlockCollection,
best_queued_number: NumberFor,
best_queued_hash: B::Hash,
role: Roles,
required_block_attributes: message::BlockAttributes,
extra_requests: ExtraRequestsAggregator,
queue_blocks: HashSet,
best_importing_number: NumberFor,
}
/// Reported sync state.
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum SyncState {
/// Initial sync is complete, keep-up sync is active.
Idle,
/// Actively catching up with the chain.
Downloading
}
/// Syncing status and statistics
#[derive(Clone)]
pub struct Status {
/// Current global sync state.
pub state: SyncState,
/// Target sync block number.
pub best_seen_block: Option>,
/// Number of peers participating in syncing.
pub num_peers: u32,
}
impl Status {
/// Whether the synchronization status is doing major downloading work or
/// is near the head of the chain.
pub fn is_major_syncing(&self) -> bool {
match self.state {
SyncState::Idle => false,
SyncState::Downloading => true,
}
}
/// Are we all alone?
pub fn is_offline(&self) -> bool {
self.num_peers == 0
}
}
impl ChainSync {
/// Create a new instance. Pass the initial known state of the chain.
pub(crate) fn new(
role: Roles,
info: &ClientInfo,
) -> Self {
let mut required_block_attributes = message::BlockAttributes::HEADER | message::BlockAttributes::JUSTIFICATION;
if role.is_full() {
required_block_attributes |= message::BlockAttributes::BODY;
}
ChainSync {
_genesis_hash: info.chain.genesis_hash,
peers: HashMap::new(),
blocks: BlockCollection::new(),
best_queued_hash: info.best_queued_hash.unwrap_or(info.chain.best_hash),
best_queued_number: info.best_queued_number.unwrap_or(info.chain.best_number),
extra_requests: ExtraRequestsAggregator::new(),
role,
required_block_attributes,
queue_blocks: Default::default(),
best_importing_number: Zero::zero(),
}
}
fn best_seen_block(&self) -> Option> {
self.peers.values().max_by_key(|p| p.best_number).map(|p| p.best_number)
}
fn state(&self, best_seen: &Option>) -> SyncState {
match best_seen {
&Some(n) if n > self.best_queued_number && n - self.best_queued_number > 5.into() => SyncState::Downloading,
_ => SyncState::Idle,
}
}
/// Returns the state of the sync of the given peer. Returns `None` if the peer is unknown.
pub(crate) fn peer_info(&self, who: &PeerId) -> Option> {
self.peers.get(who).map(|peer| {
PeerInfo {
best_hash: peer.best_hash,
best_number: peer.best_number,
}
})
}
/// Returns sync status.
pub(crate) fn status(&self) -> Status {
let best_seen = self.best_seen_block();
let state = self.state(&best_seen);
Status {
state,
best_seen_block: best_seen,
num_peers: self.peers.len() as u32,
}
}
/// Handle new connected peer. Call this method whenever we connect to a new peer.
pub(crate) fn new_peer(
&mut self,
protocol: &mut dyn Context,
who: PeerId,
info: ProtocolPeerInfo
) {
// there's nothing sync can get from the node that has no blockchain data
// (the opposite is not true, but all requests are served at protocol level)
if !info.roles.is_full() {
return;
}
let status = block_status(&*protocol.client(), &self.queue_blocks, info.best_hash);
match (status, info.best_number) {
(Err(e), _) => {
debug!(target:"sync", "Error reading blockchain: {:?}", e);
protocol.report_peer(who.clone(), BLOCKCHAIN_STATUS_READ_ERROR_REPUTATION_CHANGE);
protocol.disconnect_peer(who);
},
(Ok(BlockStatus::KnownBad), _) => {
info!("New peer with known bad best block {} ({}).", info.best_hash, info.best_number);
protocol.report_peer(who.clone(), i32::min_value());
protocol.disconnect_peer(who);
},
(Ok(BlockStatus::Unknown), b) if b.is_zero() => {
info!("New peer with unknown genesis hash {} ({}).", info.best_hash, info.best_number);
protocol.report_peer(who.clone(), i32::min_value());
protocol.disconnect_peer(who);
},
(Ok(BlockStatus::Unknown), _) if self.queue_blocks.len() > MAJOR_SYNC_BLOCKS => {
// when actively syncing the common point moves too fast.
debug!(
target:"sync",
"New peer with unknown best hash {} ({}), assuming common block.",
self.best_queued_hash,
self.best_queued_number
);
self.peers.insert(who, PeerSync {
common_number: self.best_queued_number,
best_hash: info.best_hash,
best_number: info.best_number,
state: PeerSyncState::Available,
recently_announced: Default::default(),
});
}
(Ok(BlockStatus::Unknown), _) => {
let our_best = self.best_queued_number;
if our_best.is_zero() {
// We are at genesis, just start downloading
debug!(target:"sync", "New peer with best hash {} ({}).", info.best_hash, info.best_number);
self.peers.insert(who.clone(), PeerSync {
common_number: Zero::zero(),
best_hash: info.best_hash,
best_number: info.best_number,
state: PeerSyncState::Available,
recently_announced: Default::default(),
});
self.download_new(protocol, who)
} else {
let common_best = ::std::cmp::min(our_best, info.best_number);
debug!(target:"sync",
"New peer with unknown best hash {} ({}), searching for common ancestor.",
info.best_hash,
info.best_number
);
self.peers.insert(who.clone(), PeerSync {
common_number: Zero::zero(),
best_hash: info.best_hash,
best_number: info.best_number,
state: PeerSyncState::AncestorSearch(common_best, AncestorSearchState::ExponentialBackoff(One::one())),
recently_announced: Default::default(),
});
Self::request_ancestry(protocol, who, common_best)
}
},
(Ok(BlockStatus::Queued), _) | (Ok(BlockStatus::InChainWithState), _) | (Ok(BlockStatus::InChainPruned), _) => {
debug!(target:"sync", "New peer with known best hash {} ({}).", info.best_hash, info.best_number);
self.peers.insert(who.clone(), PeerSync {
common_number: info.best_number,
best_hash: info.best_hash,
best_number: info.best_number,
state: PeerSyncState::Available,
recently_announced: Default::default(),
});
}
}
}
fn handle_ancestor_search_state(
state: AncestorSearchState,
curr_block_num: NumberFor,
block_hash_match: bool,
) -> Option<(AncestorSearchState, NumberFor)> {
let two = >::one() + >::one();
match state {
AncestorSearchState::ExponentialBackoff(next_distance_to_tip) => {
if block_hash_match && next_distance_to_tip == One::one() {
// We found the ancestor in the first step so there is no need to execute binary search.
return None;
}
if block_hash_match {
let left = curr_block_num;
let right = left + next_distance_to_tip / two;
let middle = left + (right - left) / two;
Some((AncestorSearchState::BinarySearch(left, right), middle))
} else {
let next_block_num = curr_block_num.checked_sub(&next_distance_to_tip)
.unwrap_or_else(Zero::zero);
let next_distance_to_tip = next_distance_to_tip * two;
Some((AncestorSearchState::ExponentialBackoff(next_distance_to_tip), next_block_num))
}
},
AncestorSearchState::BinarySearch(mut left, mut right) => {
if left >= curr_block_num {
return None;
}
if block_hash_match {
left = curr_block_num;
} else {
right = curr_block_num;
}
assert!(right >= left);
let middle = left + (right - left) / two;
Some((AncestorSearchState::BinarySearch(left, right), middle))
},
}
}
/// Handle a response from the remote to a block request that we made.
///
/// `request` must be the original request that triggered `response`.
///
/// If this corresponds to a valid block, this outputs the block that must be imported in the
/// import queue.
#[must_use]
pub(crate) fn on_block_data(
&mut self,
protocol: &mut dyn Context,
who: PeerId,
request: message::BlockRequest,
response: message::BlockResponse
) -> Option<(BlockOrigin, Vec>)> {
let new_blocks: Vec> = if let Some(ref mut peer) = self.peers.get_mut(&who) {
let mut blocks = response.blocks;
if request.direction == message::Direction::Descending {
trace!(target: "sync", "Reversing incoming block list");
blocks.reverse();
}
let peer_state = peer.state.clone();
match peer_state {
PeerSyncState::DownloadingNew(start_block) => {
self.blocks.clear_peer_download(&who);
peer.state = PeerSyncState::Available;
self.blocks.insert(start_block, blocks, who);
self.blocks
.drain(self.best_queued_number + One::one())
.into_iter()
.map(|block_data| {
IncomingBlock {
hash: block_data.block.hash,
header: block_data.block.header,
body: block_data.block.body,
justification: block_data.block.justification,
origin: block_data.origin,
}
}).collect()
},
PeerSyncState::DownloadingStale(_) => {
peer.state = PeerSyncState::Available;
blocks.into_iter().map(|b| {
IncomingBlock {
hash: b.hash,
header: b.header,
body: b.body,
justification: b.justification,
origin: Some(who.clone()),
}
}).collect()
},
PeerSyncState::AncestorSearch(num, state) => {
let block_hash_match = match (blocks.get(0), protocol.client().block_hash(num)) {
(Some(ref block), Ok(maybe_our_block_hash)) => {
trace!(target: "sync", "Got ancestry block #{} ({}) from peer {}", num, block.hash, who);
maybe_our_block_hash.map_or(false, |x| x == block.hash)
},
(None, _) => {
debug!(target: "sync", "Invalid response when searching for ancestor from {}", who);
protocol.report_peer(who.clone(), i32::min_value());
protocol.disconnect_peer(who);
return None
},
(_, Err(e)) => {
info!("Error answering legitimate blockchain query: {:?}", e);
protocol.report_peer(who.clone(), ANCESTRY_BLOCK_ERROR_REPUTATION_CHANGE);
protocol.disconnect_peer(who);
return None
},
};
if block_hash_match && peer.common_number < num {
peer.common_number = num;
}
if !block_hash_match && num.is_zero() {
trace!(target:"sync", "Ancestry search: genesis mismatch for peer {}", who);
protocol.report_peer(who.clone(), GENESIS_MISMATCH_REPUTATION_CHANGE);
protocol.disconnect_peer(who);
return None
}
if let Some((next_state, next_block_num)) = Self::handle_ancestor_search_state(state, num, block_hash_match) {
peer.state = PeerSyncState::AncestorSearch(next_block_num, next_state);
Self::request_ancestry(protocol, who, next_block_num);
return None
} else {
peer.state = PeerSyncState::Available;
vec![]
}
},
PeerSyncState::Available | PeerSyncState::DownloadingJustification(..) | PeerSyncState::DownloadingFinalityProof(..) => Vec::new(),
}
} else {
Vec::new()
};
let is_recent = new_blocks
.first()
.map(|block| self.peers.iter().any(|(_, peer)| peer.recently_announced.contains(&block.hash)))
.unwrap_or(false);
let origin = if is_recent { BlockOrigin::NetworkBroadcast } else { BlockOrigin::NetworkInitialSync };
if let Some((hash, number)) = new_blocks.last()
.and_then(|b| b.header.as_ref().map(|h| (b.hash.clone(), *h.number())))
{
trace!(target:"sync", "Accepted {} blocks ({:?}) with origin {:?}", new_blocks.len(), hash, origin);
self.block_queued(&hash, number);
}
self.maintain_sync(protocol);
let new_best_importing_number = new_blocks
.last()
.and_then(|b| b.header.as_ref().map(|h| h.number().clone()))
.unwrap_or_else(|| Zero::zero());
self.queue_blocks
.extend(new_blocks.iter().map(|b| b.hash.clone()));
self.best_importing_number = max(new_best_importing_number, self.best_importing_number);
Some((origin, new_blocks))
}
/// Handle a response from the remote to a justification request that we made.
///
/// `request` must be the original request that triggered `response`.
///
/// Returns `Some` if this produces a justification that must be imported into the import
/// queue.
#[must_use]
pub(crate) fn on_block_justification_data(
&mut self,
protocol: &mut dyn Context,
who: PeerId,
_request: message::BlockRequest,
response: message::BlockResponse,
) -> Option<(PeerId, B::Hash, NumberFor, Justification)> {
let peer = if let Some(peer) = self.peers.get_mut(&who) {
peer
} else {
error!(target: "sync", "Called on_block_justification_data with a bad peer ID");
return None;
};
if let PeerSyncState::DownloadingJustification(hash) = peer.state {
peer.state = PeerSyncState::Available;
// we only request one justification at a time
match response.blocks.into_iter().next() {
Some(response) => {
if hash != response.hash {
info!("Invalid block justification provided by {}: requested: {:?} got: {:?}",
who, hash, response.hash);
protocol.report_peer(who.clone(), i32::min_value());
protocol.disconnect_peer(who);
return None;
}
return self.extra_requests.justifications().on_response(
who,
response.justification,
);
},
None => {
// we might have asked the peer for a justification on a block that we thought it had
// (regardless of whether it had a justification for it or not).
trace!(target: "sync", "Peer {:?} provided empty response for justification request {:?}",
who,
hash,
);
return None;
},
}
}
self.maintain_sync(protocol);
None
}
/// Handle new finality proof data.
pub(crate) fn on_block_finality_proof_data(
&mut self,
protocol: &mut dyn Context,
who: PeerId,
response: message::FinalityProofResponse,
) -> Option<(PeerId, B::Hash, NumberFor, Vec)> {
let peer = if let Some(peer) = self.peers.get_mut(&who) {
peer
} else {
error!(target: "sync", "Called on_block_finality_proof_data with a bad peer ID");
return None;
};
if let PeerSyncState::DownloadingFinalityProof(hash) = peer.state {
peer.state = PeerSyncState::Available;
// we only request one finality proof at a time
if hash != response.block {
info!(
"Invalid block finality proof provided: requested: {:?} got: {:?}",
hash,
response.block,
);
protocol.report_peer(who.clone(), i32::min_value());
protocol.disconnect_peer(who);
return None;
}
return self.extra_requests.finality_proofs().on_response(
who,
response.proof,
);
}
self.maintain_sync(protocol);
None
}
/// A batch of blocks have been processed, with or without errors.
/// Call this when a batch of blocks have been processed by the import queue, with or without
/// errors.
pub fn blocks_processed(&mut self, protocol: &mut Context, processed_blocks: Vec, has_error: bool) {
for hash in processed_blocks {
self.queue_blocks.remove(&hash);
}
if has_error {
self.best_importing_number = Zero::zero();
}
self.maintain_sync(protocol)
}
/// Maintain the sync process (download new blocks, fetch justifications).
fn maintain_sync(&mut self, protocol: &mut dyn Context) {
let peers: Vec = self.peers.keys().map(|p| p.clone()).collect();
for peer in peers {
self.download_new(protocol, peer);
}
self.extra_requests.dispatch(&mut self.peers, protocol);
}
/// Called periodically to perform any time-based actions. Must be called at a regular
/// interval.
pub fn tick(&mut self, protocol: &mut dyn Context) {
self.extra_requests.dispatch(&mut self.peers, protocol);
}
/// Request a justification for the given block.
///
/// Uses `protocol` to queue a new justification request and tries to dispatch all pending
/// requests.
pub fn request_justification(&mut self, hash: &B::Hash, number: NumberFor, protocol: &mut dyn Context) {
self.extra_requests.justifications().queue_request(
(*hash, number),
|base, block| protocol.client().is_descendent_of(base, block),
);
self.extra_requests.justifications().dispatch(&mut self.peers, protocol);
}
/// Clears all pending justification requests.
pub fn clear_justification_requests(&mut self) {
self.extra_requests.justifications().clear();
}
/// Call this when a justification has been processed by the import queue, with or without
/// errors.
pub fn justification_import_result(&mut self, hash: B::Hash, number: NumberFor, success: bool) {
let finalization_result = if success { Ok((hash, number)) } else { Err(()) };
if !self.extra_requests.justifications().on_import_result((hash, number), finalization_result) {
debug!(target: "sync", "Got justification import result for unknown justification {:?} {:?} request.",
hash,
number,
);
}
}
/// Request a finality proof for the given block.
///
/// Queues a new finality proof request and tries to dispatch all pending requests.
pub fn request_finality_proof(&mut self, hash: &B::Hash, number: NumberFor, protocol: &mut dyn Context) {
self.extra_requests.finality_proofs().queue_request(
(*hash, number),
|base, block| protocol.client().is_descendent_of(base, block),
);
self.extra_requests.finality_proofs().dispatch(&mut self.peers, protocol);
}
pub fn finality_proof_import_result(
&mut self,
request_block: (B::Hash, NumberFor),
finalization_result: Result<(B::Hash, NumberFor), ()>,
) {
self.extra_requests.finality_proofs().on_import_result(request_block, finalization_result);
}
pub fn set_finality_proof_request_builder(&mut self, request_builder: SharedFinalityProofRequestBuilder) {
self.extra_requests.finality_proofs().essence().0 = Some(request_builder);
}
/// Notify about successful import of the given block.
pub fn block_imported(&mut self, hash: &B::Hash, number: NumberFor) {
trace!(target: "sync", "Block imported successfully {} ({})", number, hash);
}
/// Notify about finalization of the given block.
pub fn on_block_finalized(&mut self, hash: &B::Hash, number: NumberFor, protocol: &mut dyn Context) {
if let Err(err) = self.extra_requests.on_block_finalized(
hash,
number,
&|base, block| protocol.client().is_descendent_of(base, block),
) {
warn!(target: "sync", "Error cleaning up pending extra data requests: {:?}", err);
};
}
fn block_queued(&mut self, hash: &B::Hash, number: NumberFor) {
if number > self.best_queued_number {
self.best_queued_number = number;
self.best_queued_hash = *hash;
}
// Update common blocks
for (n, peer) in self.peers.iter_mut() {
if let PeerSyncState::AncestorSearch(_, _) = peer.state {
// Abort search.
peer.state = PeerSyncState::Available;
}
let new_common_number = if peer.best_number >= number {
number
} else {
peer.best_number
};
trace!(
target: "sync",
"Updating peer {} info, ours={}, common={}->{}, their best={}",
n,
number,
peer.common_number,
new_common_number,
peer.best_number,
);
peer.common_number = new_common_number;
}
}
/// Sets the new head of chain.
pub(crate) fn update_chain_info(&mut self, best_header: &B::Header) {
let hash = best_header.hash();
self.block_queued(&hash, best_header.number().clone())
}
/// Call when a node announces a new block.
///
/// If true is returned, then the caller MUST try to import passed header (call `on_block_data).
/// The network request isn't sent in this case.
#[must_use]
pub(crate) fn on_block_announce(
&mut self,
protocol: &mut dyn Context,
who: PeerId,
hash: B::Hash,
header: &B::Header,
) -> bool {
let number = *header.number();
debug!(target: "sync", "Received block announcement with number {:?}", number);
if number.is_zero() {
warn!(target: "sync", "Ignored invalid block announcement from {}: {}", who, hash);
return false;
}
let parent_status = block_status(&*protocol.client(), &self.queue_blocks, header.parent_hash().clone()).ok()
.unwrap_or(BlockStatus::Unknown);
let known_parent = parent_status != BlockStatus::Unknown;
let ancient_parent = parent_status == BlockStatus::InChainPruned;
let known = self.is_known(protocol, &hash);
let peer = if let Some(peer) = self.peers.get_mut(&who) {
peer
} else {
error!(target: "sync", "Called on_block_announce with a bad peer ID");
return false;
};
while peer.recently_announced.len() >= ANNOUNCE_HISTORY_SIZE {
peer.recently_announced.pop_front();
}
peer.recently_announced.push_back(hash.clone());
if number > peer.best_number {
// update their best block
peer.best_number = number;
peer.best_hash = hash;
}
if let PeerSyncState::AncestorSearch(_, _) = peer.state {
return false;
}
if header.parent_hash() == &self.best_queued_hash || known_parent {
peer.common_number = number - One::one();
} else if known {
peer.common_number = number
}
// known block case
if known || self.is_already_downloading(&hash) {
trace!(target: "sync", "Known block announce from {}: {}", who, hash);
return false;
}
// stale block case
let requires_additional_data = !self.role.is_light();
let stale = number <= self.best_queued_number;
if stale {
if !(known_parent || self.is_already_downloading(header.parent_hash())) {
if protocol.client().block_status(&BlockId::Number(*header.number()))
.unwrap_or(BlockStatus::Unknown) == BlockStatus::InChainPruned
{
trace!(target: "sync", "Ignored unknown ancient block announced from {}: {} {:?}", who, hash, header);
return false;
}
trace!(target: "sync", "Considering new unknown stale block announced from {}: {} {:?}", who, hash, header);
let request = self.download_unknown_stale(&who, &hash);
match request {
Some(request) => if requires_additional_data {
protocol.send_block_request(who, request);
return false;
} else {
return true;
},
None => return false,
}
} else {
if ancient_parent {
trace!(target: "sync", "Ignored ancient stale block announced from {}: {} {:?}", who, hash, header);
return false;
}
let request = self.download_stale(&who, &hash);
match request {
Some(request) => if requires_additional_data {
protocol.send_block_request(who, request);
return false;
} else {
return true;
},
None => return false,
}
}
}
if ancient_parent {
trace!(target: "sync", "Ignored ancient block announced from {}: {} {:?}", who, hash, header);
return false;
}
trace!(target: "sync", "Considering new block announced from {}: {} {:?}", who, hash, header);
let (range, request) = match self.select_new_blocks(who.clone()) {
Some((range, request)) => (range, request),
None => return false,
};
let is_required_data_available =
!requires_additional_data &&
range.end - range.start == One::one() &&
range.start == *header.number();
if !is_required_data_available {
protocol.send_block_request(who, request);
return false;
}
true
}
fn is_already_downloading(&self, hash: &B::Hash) -> bool {
self.peers.iter().any(|(_, p)| p.state == PeerSyncState::DownloadingStale(*hash))
}
fn is_known(&self, protocol: &mut dyn Context, hash: &B::Hash) -> bool {
block_status(&*protocol.client(), &self.queue_blocks, *hash).ok().map_or(false, |s| s != BlockStatus::Unknown)
}
/// Call when a peer has disconnected.
pub(crate) fn peer_disconnected(&mut self, protocol: &mut dyn Context, who: PeerId) {
self.blocks.clear_peer_download(&who);
self.peers.remove(&who);
self.extra_requests.peer_disconnected(who);
self.maintain_sync(protocol);
}
/// Restart the sync process.
pub(crate) fn restart(
&mut self,
protocol: &mut dyn Context,
mut peer_info: impl FnMut(&PeerId) -> Option>
) {
self.queue_blocks.clear();
self.best_importing_number = Zero::zero();
self.blocks.clear();
let info = protocol.client().info();
self.best_queued_hash = info.best_queued_hash.unwrap_or(info.chain.best_hash);
self.best_queued_number = info.best_queued_number.unwrap_or(info.chain.best_number);
debug!(target:"sync", "Restarted with {} ({})", self.best_queued_number, self.best_queued_hash);
let ids: Vec = self.peers.drain().map(|(id, _)| id).collect();
for id in ids {
if let Some(info) = peer_info(&id) {
self.new_peer(protocol, id, info);
}
}
}
// Download old block with known parent.
fn download_stale(
&mut self,
who: &PeerId,
hash: &B::Hash,
) -> Option> {
let peer = self.peers.get_mut(who)?;
match peer.state {
PeerSyncState::Available => {
peer.state = PeerSyncState::DownloadingStale(*hash);
Some(message::generic::BlockRequest {
id: 0,
fields: self.required_block_attributes.clone(),
from: message::FromBlock::Hash(*hash),
to: None,
direction: message::Direction::Ascending,
max: Some(1),
})
},
_ => None,
}
}
// Download old block with unknown parent.
fn download_unknown_stale(
&mut self,
who: &PeerId,
hash: &B::Hash,
) -> Option> {
let peer = self.peers.get_mut(who)?;
match peer.state {
PeerSyncState::Available => {
peer.state = PeerSyncState::DownloadingStale(*hash);
Some(message::generic::BlockRequest {
id: 0,
fields: self.required_block_attributes.clone(),
from: message::FromBlock::Hash(*hash),
to: None,
direction: message::Direction::Descending,
max: Some(MAX_UNKNOWN_FORK_DOWNLOAD_LEN),
})
},
_ => None,
}
}
// Issue a request for a peer to download new blocks, if any are available.
fn download_new(&mut self, protocol: &mut dyn Context, who: PeerId) {
if let Some((_, request)) = self.select_new_blocks(who.clone()) {
protocol.send_block_request(who, request);
}
}
// Select a range of NEW blocks to download from peer.
fn select_new_blocks(&mut self, who: PeerId) -> Option<(Range>, message::BlockRequest)> {
// when there are too many blocks in the queue => do not try to download new blocks
if self.queue_blocks.len() > MAX_IMPORTING_BLOCKS {
trace!(target: "sync", "Too many blocks in the queue.");
return None;
}
let peer = self.peers.get_mut(&who)?;
match peer.state {
PeerSyncState::Available => {
trace!(
target: "sync",
"Considering new block download from {}, common block is {}, best is {:?}",
who,
peer.common_number,
peer.best_number,
);
let range = self.blocks.needed_blocks(who.clone(), MAX_BLOCKS_TO_REQUEST, peer.best_number, peer.common_number);
match range {
Some(range) => {
trace!(target: "sync", "Requesting blocks from {}, ({} to {})", who, range.start, range.end);
let from = message::FromBlock::Number(range.start);
let max = Some((range.end - range.start).saturated_into::());
peer.state = PeerSyncState::DownloadingNew(range.start);
Some((
range,
message::generic::BlockRequest {
id: 0,
fields: self.required_block_attributes.clone(),
from,
to: None,
direction: message::Direction::Ascending,
max,
},
))
},
None => {
trace!(target: "sync", "Nothing to request");
None
},
}
},
_ => {
trace!(target: "sync", "Peer {} is busy", who);
None
},
}
}
fn request_ancestry(protocol: &mut dyn Context, who: PeerId, block: NumberFor) {
trace!(target: "sync", "Requesting ancestry block #{} from {}", block, who);
let request = message::generic::BlockRequest {
id: 0,
fields: message::BlockAttributes::HEADER | message::BlockAttributes::JUSTIFICATION,
from: message::FromBlock::Number(block),
to: None,
direction: message::Direction::Ascending,
max: Some(1),
};
protocol.send_block_request(who, request);
}
}
/// Get block status, taking into account import queue.
fn block_status(
chain: &dyn crate::chain::Client,
queue_blocks: &HashSet,
hash: B::Hash) -> Result
{
if queue_blocks.contains(&hash) {
return Ok(BlockStatus::Queued);
}
chain.block_status(&BlockId::Hash(hash))
}