// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot 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.
// Polkadot 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 Polkadot. If not, see .
//! A [`SelectChain`] implementation designed for relay chains.
//!
//! This uses information about parachains to inform GRANDPA and BABE
//! about blocks which are safe to build on and blocks which are safe to
//! finalize.
//!
//! To learn more about chain-selection rules for Relay Chains, please see the
//! documentation on [chain-selection][chain-selection-guide]
//! in the implementers' guide.
//!
//! This is mostly a wrapper around a subsystem which implements the
//! chain-selection rule, which leaves the code to be very simple.
//!
//! However, this does apply the further finality constraints to the best
//! leaf returned from the chain selection subsystem by calling into other
//! subsystems which yield information about approvals and disputes.
//!
//! [chain-selection-guide]: https://w3f.github.io/parachain-implementers-guide/protocol-chain-selection.html
#![cfg(feature = "full-node")]
use {
polkadot_primitives::v1::{
Hash, BlockNumber, Block as PolkadotBlock, Header as PolkadotHeader,
},
polkadot_subsystem::messages::{ApprovalVotingMessage, ChainSelectionMessage},
polkadot_node_subsystem_util::metrics::{self, prometheus},
polkadot_overseer::OverseerHandler,
futures::channel::oneshot,
consensus_common::{Error as ConsensusError, SelectChain},
sp_blockchain::HeaderBackend,
sp_runtime::generic::BlockId,
std::sync::Arc,
};
/// The maximum amount of unfinalized blocks we are willing to allow due to approval checking
/// or disputes.
///
/// This is a safety net that should be removed at some point in the future.
const MAX_FINALITY_LAG: polkadot_primitives::v1::BlockNumber = 50;
const LOG_TARGET: &str = "parachain::chain-selection";
/// Prometheus metrics for chain-selection.
#[derive(Debug, Default, Clone)]
pub struct Metrics(Option);
#[derive(Debug, Clone)]
struct MetricsInner {
approval_checking_finality_lag: prometheus::Gauge,
disputes_finality_lag: prometheus::Gauge,
}
impl metrics::Metrics for Metrics {
fn try_register(registry: &prometheus::Registry) -> Result {
let metrics = MetricsInner {
approval_checking_finality_lag: prometheus::register(
prometheus::Gauge::with_opts(
prometheus::Opts::new(
"parachain_approval_checking_finality_lag",
"How far behind the head of the chain the Approval Checking protocol wants to vote",
)
)?,
registry,
)?,
disputes_finality_lag: prometheus::register(
prometheus::Gauge::with_opts(
prometheus::Opts::new(
"parachain_disputes_finality_lag",
"How far behind the head of the chain the Disputes protocol wants to vote",
)
)?,
registry,
)?,
};
Ok(Metrics(Some(metrics)))
}
}
impl Metrics {
fn note_approval_checking_finality_lag(&self, lag: BlockNumber) {
if let Some(ref metrics) = self.0 {
metrics.approval_checking_finality_lag.set(lag as _);
}
}
fn note_disputes_finality_lag(&self, lag: BlockNumber) {
if let Some(ref metrics) = self.0 {
metrics.disputes_finality_lag.set(lag as _);
}
}
}
/// A chain-selection implementation which provides safety for relay chains.
pub struct SelectRelayChain {
backend: Arc,
overseer: OverseerHandler,
// A fallback to use in case the overseer is disconnected.
//
// This is used on relay chains which have not yet enabled
// parachains as well as situations where the node is offline.
fallback: sc_consensus::LongestChain,
metrics: Metrics,
}
impl SelectRelayChain
where B: sc_client_api::backend::Backend + 'static
{
/// Create a new [`SelectRelayChain`] wrapping the given chain backend
/// and a handle to the overseer.
#[allow(unused)]
pub fn new(backend: Arc, overseer: OverseerHandler, metrics: Metrics) -> Self {
SelectRelayChain {
fallback: sc_consensus::LongestChain::new(backend.clone()),
backend,
overseer,
metrics,
}
}
fn block_header(&self, hash: Hash) -> Result {
match self.backend.blockchain().header(BlockId::Hash(hash)) {
Ok(Some(header)) => Ok(header),
Ok(None) => Err(ConsensusError::ChainLookup(format!(
"Missing header with hash {:?}",
hash,
))),
Err(e) => Err(ConsensusError::ChainLookup(format!(
"Lookup failed for header with hash {:?}: {:?}",
hash,
e,
))),
}
}
fn block_number(&self, hash: Hash) -> Result {
match self.backend.blockchain().number(hash) {
Ok(Some(number)) => Ok(number),
Ok(None) => Err(ConsensusError::ChainLookup(format!(
"Missing number with hash {:?}",
hash,
))),
Err(e) => Err(ConsensusError::ChainLookup(format!(
"Lookup failed for number with hash {:?}: {:?}",
hash,
e,
))),
}
}
}
impl SelectRelayChain {
/// Given an overseer handler, this connects the [`SelectRelayChain`]'s
/// internal handler to the same overseer.
#[allow(unused)]
pub fn connect_overseer_handler(
&mut self,
other_handler: &OverseerHandler,
) {
other_handler.connect_other(&mut self.overseer);
}
}
impl Clone for SelectRelayChain
where B: sc_client_api::backend::Backend + 'static
{
fn clone(&self) -> SelectRelayChain {
SelectRelayChain {
backend: self.backend.clone(),
overseer: self.overseer.clone(),
fallback: self.fallback.clone(),
metrics: self.metrics.clone(),
}
}
}
#[derive(thiserror::Error, Debug)]
enum Error {
// A request to the subsystem was canceled.
#[error("Overseer is disconnected from Chain Selection")]
OverseerDisconnected(oneshot::Canceled),
/// Chain selection returned empty leaves.
#[error("ChainSelection returned no leaves")]
EmptyLeaves,
}
#[async_trait::async_trait]
impl SelectChain for SelectRelayChain
where B: sc_client_api::backend::Backend + 'static
{
/// Get all leaves of the chain, i.e. block hashes that are suitable to
/// build upon and have no suitable children.
async fn leaves(&self) -> Result, ConsensusError> {
if self.overseer.is_disconnected() {
return self.fallback.leaves().await
}
let (tx, rx) = oneshot::channel();
self.overseer
.clone()
.send_msg(
ChainSelectionMessage::Leaves(tx),
std::any::type_name::(),
).await;
rx.await
.map_err(Error::OverseerDisconnected)
.map_err(|e| ConsensusError::Other(Box::new(e)))
}
/// Among all leaves, pick the one which is the best chain to build upon.
async fn best_chain(&self) -> Result {
if self.overseer.is_disconnected() {
return self.fallback.best_chain().await
}
// The Chain Selection subsystem is supposed to treat the finalized
// block as the best leaf in the case that there are no viable
// leaves, so this should not happen in practice.
let best_leaf = self.leaves()
.await?
.first()
.ok_or_else(|| ConsensusError::Other(Box::new(Error::EmptyLeaves)))?
.clone();
self.block_header(best_leaf)
}
/// Get the best descendent of `target_hash` that we should attempt to
/// finalize next, if any. It is valid to return the `target_hash` if
/// no better block exists.
///
/// This will search all leaves to find the best one containing the
/// given target hash, and then constrain to the given block number.
///
/// It will also constrain the chain to only chains which are fully
/// approved, and chains which contain no disputes.
async fn finality_target(
&self,
target_hash: Hash,
maybe_max_number: Option,
) -> Result