// Copyright 2017-2020 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.
//!
use codec::Encode;
use blocks::BlockCollection;
use sp_blockchain::{Error as ClientError, Info as BlockchainInfo, HeaderMetadata};
use sp_consensus::{BlockOrigin, BlockStatus,
block_validation::{BlockAnnounceValidator, Validation},
import_queue::{IncomingBlock, BlockImportResult, BlockImportError}
};
use crate::{
config::BoxFinalityProofRequestBuilder,
protocol::message::{self, generic::FinalityProofRequest, BlockAnnounce, BlockAttributes, BlockRequest, BlockResponse,
FinalityProofResponse, Roles},
};
use either::Either;
use extra_requests::ExtraRequests;
use libp2p::PeerId;
use log::{debug, trace, warn, info, error};
use sp_runtime::{
Justification,
generic::BlockId,
traits::{Block as BlockT, Header, NumberFor, Zero, One, CheckedSub, SaturatedConversion, Hash, HashFor}
};
use sp_arithmetic::traits::Saturating;
use std::{
fmt, ops::Range, collections::{HashMap, hash_map::Entry, HashSet, VecDeque},
sync::Arc, pin::Pin,
};
use futures::{task::Poll, Future, stream::FuturesUnordered, FutureExt, StreamExt};
mod blocks;
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;
/// Maximum blocks to download ahead of any gap.
const MAX_DOWNLOAD_AHEAD: u32 = 2048;
/// Maximum number of concurrent block announce validations.
///
/// If the queue reaches the maximum, we drop any new block
/// announcements.
const MAX_CONCURRENT_BLOCK_ANNOUNCE_VALIDATIONS: usize = 256;
/// Maximum number of concurrent block announce validations per peer.
///
/// See [`MAX_CONCURRENT_BLOCK_ANNOUNCE_VALIDATIONS`] for more information.
const MAX_CONCURRENT_BLOCK_ANNOUNCE_VALIDATIONS_PER_PEER: usize = 4;
/// We use a heuristic that with a high likelihood, by the time
/// `MAJOR_SYNC_BLOCKS` have been imported we'll be on the same
/// chain as (or at least closer to) the peer so we want to delay
/// the ancestor search to not waste time doing that when we are
/// so far behind.
const MAJOR_SYNC_BLOCKS: u8 = 5;
/// Number of recently announced blocks to track for each peer.
const ANNOUNCE_HISTORY_SIZE: usize = 64;
mod rep {
use sc_peerset::ReputationChange as Rep;
/// Reputation change when a peer sent us a message that led to a
/// database read error.
pub const BLOCKCHAIN_READ_ERROR: Rep = Rep::new(-(1 << 16), "DB Error");
/// Reputation change when a peer sent us a status message with a different
/// genesis than us.
pub const GENESIS_MISMATCH: Rep = Rep::new(i32::min_value(), "Genesis mismatch");
/// Reputation change for peers which send us a block with an incomplete header.
pub const INCOMPLETE_HEADER: Rep = Rep::new(-(1 << 20), "Incomplete header");
/// Reputation change for peers which send us a block which we fail to verify.
pub const VERIFICATION_FAIL: Rep = Rep::new(-(1 << 29), "Block verification failed");
/// Reputation change for peers which send us a known bad block.
pub const BAD_BLOCK: Rep = Rep::new(-(1 << 29), "Bad block");
/// Peer did not provide us with advertised block data.
pub const NO_BLOCK: Rep = Rep::new(-(1 << 29), "No requested block data");
/// Reputation change for peers which send us a known block.
pub const KNOWN_BLOCK: Rep = Rep::new(-(1 << 29), "Duplicate block");
/// Reputation change for peers which send us a block with bad justifications.
pub const BAD_JUSTIFICATION: Rep = Rep::new(-(1 << 16), "Bad justification");
/// Reputation change for peers which send us a block with bad finality proof.
pub const BAD_FINALITY_PROOF: Rep = Rep::new(-(1 << 16), "Bad finality proof");
/// Reputation change when a peer sent us invlid ancestry result.
pub const UNKNOWN_ANCESTOR:Rep = Rep::new(-(1 << 16), "DB Error");
}
enum PendingRequests {
Some(HashSet),
All,
}
impl PendingRequests {
fn add(&mut self, id: &PeerId) {
match self {
PendingRequests::Some(set) => {
set.insert(id.clone());
}
PendingRequests::All => {},
}
}
fn take(&mut self) -> PendingRequests {
std::mem::take(self)
}
fn set_all(&mut self) {
*self = PendingRequests::All;
}
fn contains(&self, id: &PeerId) -> bool {
match self {
PendingRequests::Some(set) => set.contains(id),
PendingRequests::All => true,
}
}
fn is_empty(&self) -> bool {
match self {
PendingRequests::Some(set) => set.is_empty(),
PendingRequests::All => false,
}
}
}
impl Default for PendingRequests {
fn default() -> Self {
PendingRequests::Some(HashSet::default())
}
}
/// The main data structure which contains all the state for a chains
/// active syncing strategy.
pub struct ChainSync {
/// Chain client.
client: Arc>,
/// The active peers that we are using to sync and their PeerSync status
peers: HashMap>,
/// A `BlockCollection` of blocks that are being downloaded from peers
blocks: BlockCollection,
/// The best block number in our queue of blocks to import
best_queued_number: NumberFor,
/// The best block hash in our queue of blocks to import
best_queued_hash: B::Hash,
/// The role of this node, e.g. light or full
role: Roles,
/// What block attributes we require for this node, usually derived from
/// what role we are, but could be customized
required_block_attributes: message::BlockAttributes,
/// Any extra finality proof requests.
extra_finality_proofs: ExtraRequests,
/// Any extra justification requests.
extra_justifications: ExtraRequests,
/// A set of hashes of blocks that are being downloaded or have been
/// downloaded and are queued for import.
queue_blocks: HashSet,
/// The best block number that was successfully imported into the chain.
/// This can not decrease.
best_imported_number: NumberFor,
/// Finality proof handler.
request_builder: Option>,
/// Fork sync targets.
fork_targets: HashMap>,
/// A set of peers for which there might be potential block requests
pending_requests: PendingRequests,
/// A type to check incoming block announcements.
block_announce_validator: Box + Send>,
/// Maximum number of peers to ask the same blocks in parallel.
max_parallel_downloads: u32,
/// Total number of downloaded blocks.
downloaded_blocks: usize,
/// All block announcement that are currently being validated.
block_announce_validation: FuturesUnordered<
Pin> + Send>>
>,
/// Stats per peer about the number of concurrent block announce validations.
block_announce_validation_per_peer_stats: HashMap,
}
/// All the data we have about a Peer that we are trying to sync with
#[derive(Debug, Clone)]
pub struct PeerSync {
/// The common number is the block number that is a common point of
/// ancestry for both our chains (as far as we know).
pub common_number: NumberFor,
/// The hash of the best block that we've seen for this peer.
pub best_hash: B::Hash,
/// The number of the best block that we've seen for this peer.
pub best_number: NumberFor,
/// The state of syncing this peer is in for us, generally categories
/// into `Available` or "busy" with something as defined by `PeerSyncState`.
pub state: PeerSyncState,
/// A queue of blocks that this peer has announced to us, should only
/// contain `ANNOUNCE_HISTORY_SIZE` entries.
pub recently_announced: VecDeque
}
/// The sync status of a peer we are trying to sync with
#[derive(Debug)]
pub struct PeerInfo {
/// Their best block hash.
pub best_hash: B::Hash,
/// Their best block number.
pub best_number: NumberFor
}
struct ForkTarget {
number: NumberFor,
parent_hash: Option,
peers: HashSet,
}
/// The state of syncing between a Peer and ourselves.
///
/// Generally two categories, "busy" or `Available`. If busy, the enum
/// defines what we are busy with.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum PeerSyncState {
/// Available for sync requests.
Available,
/// Searching for ancestors the Peer has in common with us.
AncestorSearch {
start: NumberFor,
current: NumberFor,
state: AncestorSearchState,
},
/// Actively downloading new blocks, starting from the given Number.
DownloadingNew(NumberFor),
/// Downloading a stale block with given Hash. Stale means that it is a
/// block with a number that is lower than our best number. It might be
/// from a fork and not necessarily already imported.
DownloadingStale(B::Hash),
/// Downloading justification for given block hash.
DownloadingJustification(B::Hash),
/// Downloading finality proof for given block hash.
DownloadingFinalityProof(B::Hash)
}
impl PeerSyncState {
pub fn is_available(&self) -> bool {
if let PeerSyncState::Available = self {
true
} else {
false
}
}
}
/// 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,
/// Number of blocks queued for import
pub queued_blocks: u32,
}
/// A peer did not behave as expected and should be reported.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BadPeer(pub PeerId, pub sc_peerset::ReputationChange);
impl fmt::Display for BadPeer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Bad peer {}; Reputation change: {:?}", self.0, self.1)
}
}
impl std::error::Error for BadPeer {}
/// Result of [`ChainSync::on_block_data`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OnBlockData {
/// The block should be imported.
Import(BlockOrigin, Vec>),
/// A new block request needs to be made to the given peer.
Request(PeerId, BlockRequest)
}
/// Result of [`ChainSync::poll_block_announce_validation`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PollBlockAnnounceValidation {
/// The announcement failed at validation.
///
/// The peer reputation should be decreased.
Failure {
/// Who sent the processed block announcement?
who: PeerId,
},
/// The announcement does not require further handling.
Nothing {
/// Who sent the processed block announcement?
who: PeerId,
/// Was this their new best block?
is_best: bool,
/// The header of the announcement.
header: H,
},
/// The announcement header should be imported.
ImportHeader {
/// Who sent the processed block announcement?
who: PeerId,
/// Was this their new best block?
is_best: bool,
/// The header of the announcement.
header: H,
},
}
/// Result of [`ChainSync::block_announce_validation`].
#[derive(Debug, Clone, PartialEq, Eq)]
enum PreValidateBlockAnnounce {
/// The announcement failed at validation.
///
/// The peer reputation should be decreased.
Failure {
/// Who sent the processed block announcement?
who: PeerId,
},
/// The announcement does not require further handling.
Nothing {
/// Who sent the processed block announcement?
who: PeerId,
/// Was this their new best block?
is_best: bool,
/// The announcement.
announce: BlockAnnounce,
},
/// The pre-validation was sucessful and the announcement should be
/// further processed.
Process {
/// Is this the new best block of the peer?
is_new_best: bool,
/// The id of the peer that send us the announcement.
who: PeerId,
/// The announcement.
announce: BlockAnnounce,
},
}
/// Result of [`ChainSync::on_block_justification`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OnBlockJustification {
/// The justification needs no further handling.
Nothing,
/// The justification should be imported.
Import {
peer: PeerId,
hash: B::Hash,
number: NumberFor,
justification: Justification
}
}
/// Result of [`ChainSync::on_block_finality_proof`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OnBlockFinalityProof {
/// The proof needs no further handling.
Nothing,
/// The proof should be imported.
Import {
peer: PeerId,
hash: B::Hash,
number: NumberFor,
proof: Vec
}
}
/// Result of [`ChainSync::has_slot_for_block_announce_validation`].
enum HasSlotForBlockAnnounceValidation {
/// Yes, there is a slot for the block announce validation.
Yes,
/// We reached the total maximum number of validation slots.
TotalMaximumSlotsReached,
/// We reached the maximum number of validation slots for the given peer.
MaximumPeerSlotsReached,
}
impl ChainSync {
/// Create a new instance.
pub fn new(
role: Roles,
client: Arc>,
info: &BlockchainInfo,
request_builder: Option>,
block_announce_validator: Box + Send>,
max_parallel_downloads: u32,
) -> Self {
let mut required_block_attributes = BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION;
if role.is_full() {
required_block_attributes |= BlockAttributes::BODY
}
ChainSync {
client,
peers: HashMap::new(),
blocks: BlockCollection::new(),
best_queued_hash: info.best_hash,
best_queued_number: info.best_number,
best_imported_number: info.best_number,
extra_finality_proofs: ExtraRequests::new("finality proof"),
extra_justifications: ExtraRequests::new("justification"),
role,
required_block_attributes,
queue_blocks: Default::default(),
request_builder,
fork_targets: Default::default(),
pending_requests: Default::default(),
block_announce_validator,
max_parallel_downloads,
downloaded_blocks: 0,
block_announce_validation: Default::default(),
block_announce_validation_per_peer_stats: Default::default(),
}
}
/// Returns the state of the sync of the given peer.
///
/// Returns `None` if the peer is unknown.
pub fn peer_info(&self, who: &PeerId) -> Option> {
self.peers.get(who).map(|p| PeerInfo { best_hash: p.best_hash, best_number: p.best_number })
}
/// Returns the current sync status.
pub fn status(&self) -> Status {
let best_seen = self.peers.values().map(|p| p.best_number).max();
let sync_state =
if let Some(n) = best_seen {
// A chain is classified as downloading if the provided best block is
// more than `MAJOR_SYNC_BLOCKS` behind the best queued block.
if n > self.best_queued_number && n - self.best_queued_number > MAJOR_SYNC_BLOCKS.into() {
SyncState::Downloading
} else {
SyncState::Idle
}
} else {
SyncState::Idle
};
Status {
state: sync_state,
best_seen_block: best_seen,
num_peers: self.peers.len() as u32,
queued_blocks: self.queue_blocks.len() as u32,
}
}
/// Number of active sync requests.
pub fn num_sync_requests(&self) -> usize {
self.fork_targets.len()
}
/// Number of downloaded blocks.
pub fn num_downloaded_blocks(&self) -> usize {
self.downloaded_blocks
}
/// Handle a new connected peer.
///
/// Call this method whenever we connect to a new peer.
pub fn new_peer(&mut self, who: PeerId, best_hash: B::Hash, best_number: NumberFor)
-> Result