mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-25 08:15:44 +00:00
Add documentation to the main sync code (#2879)
* Added some documentation and question on the main sync code * Remove strange formatting change Co-Authored-By: André Silva <andre.beat@gmail.com> * Improve comment per suggestion * Remove comment about re-using PeerInfo, no real reason and no real consensus if it's better or worse. * Fix redundancy in comment Co-Authored-By: André Silva <andre.beat@gmail.com> * Explaining more things and fixing review comments. * Better explanation of MAJOR_SYNC_BLOCKS * Explains the ancestor search strategy a bit * Corrects explanation of what a stale block is * Adds a bit of context for `request_ancestry` function * Revert code change to not confuse the PR
This commit is contained in:
committed by
André Silva
parent
72278748ab
commit
925a90f57f
@@ -53,15 +53,17 @@ use std::collections::HashSet;
|
|||||||
mod blocks;
|
mod blocks;
|
||||||
mod extra_requests;
|
mod extra_requests;
|
||||||
|
|
||||||
// Maximum blocks to request in a single packet.
|
/// Maximum blocks to request in a single packet.
|
||||||
const MAX_BLOCKS_TO_REQUEST: usize = 128;
|
const MAX_BLOCKS_TO_REQUEST: usize = 128;
|
||||||
// Maximum blocks to store in the import queue.
|
/// Maximum blocks to store in the import queue.
|
||||||
const MAX_IMPORTING_BLOCKS: usize = 2048;
|
const MAX_IMPORTING_BLOCKS: usize = 2048;
|
||||||
// Number of blocks in the queue that prevents ancestry search.
|
/// 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're so far behind.
|
||||||
const MAJOR_SYNC_BLOCKS: usize = 5;
|
const MAJOR_SYNC_BLOCKS: usize = 5;
|
||||||
// Number of recently announced blocks to track for each peer.
|
/// Number of recently announced blocks to track for each peer.
|
||||||
const ANNOUNCE_HISTORY_SIZE: usize = 64;
|
const ANNOUNCE_HISTORY_SIZE: usize = 64;
|
||||||
// Max number of blocks to download for unknown forks.
|
/// Max number of blocks to download for unknown forks.
|
||||||
const MAX_UNKNOWN_FORK_DOWNLOAD_LEN: u32 = 32;
|
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.
|
/// 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);
|
const BLOCKCHAIN_STATUS_READ_ERROR_REPUTATION_CHANGE: i32 = -(1 << 16);
|
||||||
@@ -90,15 +92,24 @@ pub trait Context<B: BlockT> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
/// All the data we have about a Peer that we are trying to sync with
|
||||||
pub(crate) struct PeerSync<B: BlockT> {
|
pub(crate) struct PeerSync<B: BlockT> {
|
||||||
|
/// 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<B>,
|
pub common_number: NumberFor<B>,
|
||||||
|
/// The hash of the best block that we've seen for this peer
|
||||||
pub best_hash: B::Hash,
|
pub best_hash: B::Hash,
|
||||||
|
/// The number of the best block that we've seen for this peer
|
||||||
pub best_number: NumberFor<B>,
|
pub best_number: NumberFor<B>,
|
||||||
|
/// 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<B>,
|
pub state: PeerSyncState<B>,
|
||||||
|
/// A queue of blocks that this peer has announced to us, should only contain
|
||||||
|
/// `ANNOUNCE_HISTORY_SIZE` entries.
|
||||||
pub recently_announced: VecDeque<B::Hash>,
|
pub recently_announced: VecDeque<B::Hash>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peer sync status.
|
/// The sync status of a peer we are trying to sync with
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct PeerInfo<B: BlockT> {
|
pub(crate) struct PeerInfo<B: BlockT> {
|
||||||
/// Their best block hash.
|
/// Their best block hash.
|
||||||
@@ -108,6 +119,8 @@ pub(crate) struct PeerInfo<B: BlockT> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
|
/// The ancestor search state expresses which algorithm, and its stateful parameters, we are using to
|
||||||
|
/// try to find an ancestor block
|
||||||
pub(crate) enum AncestorSearchState<B: BlockT> {
|
pub(crate) enum AncestorSearchState<B: BlockT> {
|
||||||
/// Use exponential backoff to find an ancestor, then switch to binary search.
|
/// Use exponential backoff to find an ancestor, then switch to binary search.
|
||||||
/// We keep track of the exponent.
|
/// We keep track of the exponent.
|
||||||
@@ -118,26 +131,45 @@ pub(crate) enum AncestorSearchState<B: BlockT> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
|
/// 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.
|
||||||
pub(crate) enum PeerSyncState<B: BlockT> {
|
pub(crate) enum PeerSyncState<B: BlockT> {
|
||||||
|
/// Searching for ancestors the Peer has in common with us.
|
||||||
AncestorSearch(NumberFor<B>, AncestorSearchState<B>),
|
AncestorSearch(NumberFor<B>, AncestorSearchState<B>),
|
||||||
|
/// Available for sync requests.
|
||||||
Available,
|
Available,
|
||||||
|
/// Actively downloading new blocks, starting from the given Number.
|
||||||
DownloadingNew(NumberFor<B>),
|
DownloadingNew(NumberFor<B>),
|
||||||
|
/// Downloading a stale block with given Hash. Stale means that it's 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),
|
DownloadingStale(B::Hash),
|
||||||
|
/// Downloading justification for given block hash.
|
||||||
DownloadingJustification(B::Hash),
|
DownloadingJustification(B::Hash),
|
||||||
|
/// Downloading finality proof for given block hash.
|
||||||
DownloadingFinalityProof(B::Hash),
|
DownloadingFinalityProof(B::Hash),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Relay chain sync strategy.
|
/// The main data structure to contain all the state for a chains active syncing strategy.
|
||||||
pub struct ChainSync<B: BlockT> {
|
pub struct ChainSync<B: BlockT> {
|
||||||
|
/// The active peers that we are using to sync and their PeerSync status
|
||||||
peers: HashMap<PeerId, PeerSync<B>>,
|
peers: HashMap<PeerId, PeerSync<B>>,
|
||||||
|
/// A `BlockCollection` of blocks that are being downloaded from peers
|
||||||
blocks: BlockCollection<B>,
|
blocks: BlockCollection<B>,
|
||||||
|
/// The best block number in our queue of blocks to import
|
||||||
best_queued_number: NumberFor<B>,
|
best_queued_number: NumberFor<B>,
|
||||||
|
/// The best block hash in our queue of blocks to import
|
||||||
best_queued_hash: B::Hash,
|
best_queued_hash: B::Hash,
|
||||||
|
/// The role of this node, e.g. light or full
|
||||||
role: Roles,
|
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,
|
required_block_attributes: message::BlockAttributes,
|
||||||
extra_finality_proofs: ExtraRequests<B>,
|
extra_finality_proofs: ExtraRequests<B>,
|
||||||
extra_justifications: ExtraRequests<B>,
|
extra_justifications: ExtraRequests<B>,
|
||||||
|
/// A set of hashes of blocks that are being downloaded or have been downloaded and are queued
|
||||||
|
/// for import.
|
||||||
queue_blocks: HashSet<B::Hash>,
|
queue_blocks: HashSet<B::Hash>,
|
||||||
|
/// The best block number that we are currently importing
|
||||||
best_importing_number: NumberFor<B>,
|
best_importing_number: NumberFor<B>,
|
||||||
request_builder: Option<SharedFinalityProofRequestBuilder<B>>,
|
request_builder: Option<SharedFinalityProofRequestBuilder<B>>,
|
||||||
}
|
}
|
||||||
@@ -203,10 +235,14 @@ impl<B: BlockT> ChainSync<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number for the best seen blocks among connected peers, if any
|
||||||
fn best_seen_block(&self) -> Option<NumberFor<B>> {
|
fn best_seen_block(&self) -> Option<NumberFor<B>> {
|
||||||
self.peers.values().max_by_key(|p| p.best_number).map(|p| p.best_number)
|
self.peers.values().max_by_key(|p| p.best_number).map(|p| p.best_number)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the SyncState that we are currently in based on a provided `best_seen` block number.
|
||||||
|
/// A chain is classified as downloading if the provided best block is more than `MAJOR_SYNC_BLOCKS`
|
||||||
|
/// behind the best queued block.
|
||||||
fn state(&self, best_seen: &Option<NumberFor<B>>) -> SyncState {
|
fn state(&self, best_seen: &Option<NumberFor<B>>) -> SyncState {
|
||||||
match best_seen {
|
match best_seen {
|
||||||
&Some(n) if n > self.best_queued_number && n - self.best_queued_number > 5.into() => SyncState::Downloading,
|
&Some(n) if n > self.best_queued_number && n - self.best_queued_number > 5.into() => SyncState::Downloading,
|
||||||
@@ -266,7 +302,9 @@ impl<B: BlockT> ChainSync<B> {
|
|||||||
protocol.disconnect_peer(who);
|
protocol.disconnect_peer(who);
|
||||||
},
|
},
|
||||||
(Ok(BlockStatus::Unknown), _) if self.queue_blocks.len() > MAJOR_SYNC_BLOCKS => {
|
(Ok(BlockStatus::Unknown), _) if self.queue_blocks.len() > MAJOR_SYNC_BLOCKS => {
|
||||||
// when actively syncing the common point moves too fast.
|
// If there are more than `MAJOR_SYNC_BLOCKS` in the import queue then we have
|
||||||
|
// enough to do in the import queue that it's not worth kicking off
|
||||||
|
// an ancestor search, which is what we do in the next match case below.
|
||||||
debug!(
|
debug!(
|
||||||
target:"sync",
|
target:"sync",
|
||||||
"New peer with unknown best hash {} ({}), assuming common block.",
|
"New peer with unknown best hash {} ({}), assuming common block.",
|
||||||
@@ -329,6 +367,13 @@ impl<B: BlockT> ChainSync<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This function handles the ancestor search strategy used. The goal is to find a common point
|
||||||
|
/// that both our chains agree on that is as close to the tip as possible.
|
||||||
|
/// The way this works is we first have an exponential backoff strategy, where we try to step
|
||||||
|
/// forward until we find a block hash mismatch. The size of the step doubles each step we take.
|
||||||
|
///
|
||||||
|
/// When we've found a block hash mismatch we then fall back to a binary search between the two
|
||||||
|
/// last known points to find the common block closest to the tip.
|
||||||
fn handle_ancestor_search_state(
|
fn handle_ancestor_search_state(
|
||||||
state: AncestorSearchState<B>,
|
state: AncestorSearchState<B>,
|
||||||
curr_block_num: NumberFor<B>,
|
curr_block_num: NumberFor<B>,
|
||||||
@@ -690,7 +735,7 @@ impl<B: BlockT> ChainSync<B> {
|
|||||||
self.request_builder = Some(builder)
|
self.request_builder = Some(builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Notify about successful import of the given block.
|
/// Log that a block has been successfully imported
|
||||||
pub fn block_imported(&mut self, hash: &B::Hash, number: NumberFor<B>) {
|
pub fn block_imported(&mut self, hash: &B::Hash, number: NumberFor<B>) {
|
||||||
trace!(target: "sync", "Block imported successfully {} ({})", number, hash);
|
trace!(target: "sync", "Block imported successfully {} ({})", number, hash);
|
||||||
}
|
}
|
||||||
@@ -714,6 +759,8 @@ impl<B: BlockT> ChainSync<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called when a block has been queued for import. Updates our internal state for best queued
|
||||||
|
/// block and then goes through all peers to update our view of their state as well.
|
||||||
fn block_queued(&mut self, hash: &B::Hash, number: NumberFor<B>) {
|
fn block_queued(&mut self, hash: &B::Hash, number: NumberFor<B>) {
|
||||||
if number > self.best_queued_number {
|
if number > self.best_queued_number {
|
||||||
self.best_queued_number = number;
|
self.best_queued_number = number;
|
||||||
@@ -743,7 +790,8 @@ impl<B: BlockT> ChainSync<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the new head of chain.
|
/// Signal that `best_header` has been queued for import and update the `ChainSync` state with
|
||||||
|
/// that information.
|
||||||
pub(crate) fn update_chain_info(&mut self, best_header: &B::Header) {
|
pub(crate) fn update_chain_info(&mut self, best_header: &B::Header) {
|
||||||
let hash = best_header.hash();
|
let hash = best_header.hash();
|
||||||
self.block_queued(&hash, best_header.number().clone())
|
self.block_queued(&hash, best_header.number().clone())
|
||||||
@@ -751,8 +799,9 @@ impl<B: BlockT> ChainSync<B> {
|
|||||||
|
|
||||||
/// Call when a node announces a new block.
|
/// 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).
|
/// 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.
|
/// The network request isn't sent in this case.
|
||||||
|
/// Both hash and header is passed as an optimization to avoid rehashing the header.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn on_block_announce(
|
pub(crate) fn on_block_announce(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -791,6 +840,8 @@ impl<B: BlockT> ChainSync<B> {
|
|||||||
if let PeerSyncState::AncestorSearch(_, _) = peer.state {
|
if let PeerSyncState::AncestorSearch(_, _) = peer.state {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// We assume that the announced block is the latest they have seen, and so our common number
|
||||||
|
// is either one further ahead or it's the one they just announced, if we know about it.
|
||||||
if header.parent_hash() == &self.best_queued_hash || known_parent {
|
if header.parent_hash() == &self.best_queued_hash || known_parent {
|
||||||
peer.common_number = number - One::one();
|
peer.common_number = number - One::one();
|
||||||
} else if known {
|
} else if known {
|
||||||
@@ -805,8 +856,7 @@ impl<B: BlockT> ChainSync<B> {
|
|||||||
|
|
||||||
// stale block case
|
// stale block case
|
||||||
let requires_additional_data = !self.role.is_light();
|
let requires_additional_data = !self.role.is_light();
|
||||||
let stale = number <= self.best_queued_number;
|
if number <= self.best_queued_number {
|
||||||
if stale {
|
|
||||||
if !(known_parent || self.is_already_downloading(header.parent_hash())) {
|
if !(known_parent || self.is_already_downloading(header.parent_hash())) {
|
||||||
if protocol.client().block_status(&BlockId::Number(*header.number()))
|
if protocol.client().block_status(&BlockId::Number(*header.number()))
|
||||||
.unwrap_or(BlockStatus::Unknown) == BlockStatus::InChainPruned
|
.unwrap_or(BlockStatus::Unknown) == BlockStatus::InChainPruned
|
||||||
@@ -879,10 +929,14 @@ impl<B: BlockT> ChainSync<B> {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience function to iterate through all peers and see if there are any that we are
|
||||||
|
/// downloading this hash from.
|
||||||
fn is_already_downloading(&self, hash: &B::Hash) -> bool {
|
fn is_already_downloading(&self, hash: &B::Hash) -> bool {
|
||||||
self.peers.iter().any(|(_, p)| p.state == PeerSyncState::DownloadingStale(*hash))
|
self.peers.iter().any(|(_, p)| p.state == PeerSyncState::DownloadingStale(*hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the block with given hash exists in the import queue with known status or is
|
||||||
|
/// already imported.
|
||||||
fn is_known(&self, protocol: &mut dyn Context<B>, hash: &B::Hash) -> bool {
|
fn is_known(&self, protocol: &mut dyn Context<B>, hash: &B::Hash) -> bool {
|
||||||
block_status(&*protocol.client(), &self.queue_blocks, *hash).ok().map_or(false, |s| s != BlockStatus::Unknown)
|
block_status(&*protocol.client(), &self.queue_blocks, *hash).ok().map_or(false, |s| s != BlockStatus::Unknown)
|
||||||
}
|
}
|
||||||
@@ -1025,6 +1079,8 @@ impl<B: BlockT> ChainSync<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request the ancestry for a block. Sends a request for header and justification for the given
|
||||||
|
/// block number. Used during ancestry search.
|
||||||
fn request_ancestry(protocol: &mut dyn Context<B>, who: PeerId, block: NumberFor<B>) {
|
fn request_ancestry(protocol: &mut dyn Context<B>, who: PeerId, block: NumberFor<B>) {
|
||||||
trace!(target: "sync", "Requesting ancestry block #{} from {}", block, who);
|
trace!(target: "sync", "Requesting ancestry block #{} from {}", block, who);
|
||||||
let request = message::generic::BlockRequest {
|
let request = message::generic::BlockRequest {
|
||||||
@@ -1039,7 +1095,8 @@ impl<B: BlockT> ChainSync<B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get block status, taking into account import queue.
|
/// Returns the BlockStatus for given block hash, looking first in the import queue and then in the
|
||||||
|
/// provided chain.
|
||||||
fn block_status<B: BlockT>(
|
fn block_status<B: BlockT>(
|
||||||
chain: &dyn crate::chain::Client<B>,
|
chain: &dyn crate::chain::Client<B>,
|
||||||
queue_blocks: &HashSet<B::Hash>,
|
queue_blocks: &HashSet<B::Hash>,
|
||||||
|
|||||||
Reference in New Issue
Block a user