// 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.
//!
use blocks::BlockCollection;
use client_api::{ClientInfo, error::Error as ClientError};
use consensus::{BlockOrigin, BlockStatus,
block_validation::{BlockAnnounceValidator, Validation},
import_queue::{IncomingBlock, BlockImportResult, BlockImportError}
};
use crate::{
config::{Roles, BoxFinalityProofRequestBuilder},
message::{self, generic::FinalityProofRequest, BlockAnnounce, BlockAttributes, BlockRequest, BlockResponse,
FinalityProofResponse},
};
use either::Either;
use extra_requests::ExtraRequests;
use libp2p::PeerId;
use log::{debug, trace, warn, info, error};
use sr_primitives::{
Justification,
generic::BlockId,
traits::{Block as BlockT, Header, NumberFor, Zero, One, CheckedSub, SaturatedConversion}
};
use std::{fmt, ops::Range, collections::{HashMap, HashSet, VecDeque}, sync::Arc};
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;
/// 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;
/// 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;
/// Reputation change for peers which send us a block with an incomplete header.
const INCOMPLETE_HEADER_REPUTATION_CHANGE: i32 = -(1 << 20);
/// Reputation change for peers which send us a block which we fail to verify.
const VERIFICATION_FAIL_REPUTATION_CHANGE: i32 = -(1 << 20);
/// Reputation change for peers which send us a bad block.
const BAD_BLOCK_REPUTATION_CHANGE: i32 = -(1 << 29);
/// Reputation change for peers which send us a block with bad justifications.
const BAD_JUSTIFICATION_REPUTATION_CHANGE: i32 = -(1 << 16);
/// 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 flag that caches idle state with no pending requests.
is_idle: bool,
/// 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,
}
/// 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(NumberFor, 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 i32);
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::on_block_announce`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OnBlockAnnounce {
/// The announcement does not require further handling.
Nothing,
/// The announcement header should be imported.
ImportHeader,
}
/// 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
}
}
impl ChainSync {
/// Create a new instance.
pub fn new(
role: Roles,
client: Arc>,
info: &ClientInfo,
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.chain.best_hash,
best_queued_number: info.chain.best_number,
best_imported_number: info.chain.best_number,
extra_finality_proofs: ExtraRequests::new(),
extra_justifications: ExtraRequests::new(),
role,
required_block_attributes,
queue_blocks: Default::default(),
request_builder,
fork_targets: Default::default(),
is_idle: false,
block_announce_validator,
max_parallel_downloads,
}
}
/// 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().max_by_key(|p| p.best_number).map(|p| p.best_number);
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,
}
}
/// 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