#![deny(unused_results)]
pub mod benchmarking;
pub mod chain_spec;
mod fake_runtime_api;
mod grandpa_support;
mod parachains_db;
mod relay_chain_selection;
#[cfg(feature = "full-node")]
pub mod builder;
#[cfg(feature = "full-node")]
pub mod overseer;
#[cfg(feature = "full-node")]
pub mod workers;
#[cfg(feature = "full-node")]
pub use crate::builder::{new_full, NewFull, NewFullParams};
#[cfg(feature = "full-node")]
pub use self::overseer::{
CollatorOverseerGen, ExtendedOverseerGenArgs, OverseerGen, OverseerGenArgs,
ValidatorOverseerGen,
};
#[cfg(test)]
mod tests;
#[cfg(feature = "full-node")]
use crate::builder::{new_partial, new_partial_basics};
#[cfg(feature = "full-node")]
use {
polkadot_node_core_approval_voting as approval_voting_subsystem,
polkadot_node_core_av_store::Error as AvailabilityError,
polkadot_node_core_chain_selection as chain_selection_subsystem,
};
use polkadot_node_subsystem_util::database::Database;
use polkadot_overseer::SpawnGlue;
#[cfg(feature = "full-node")]
pub use {
polkadot_overseer::{Handle, Overseer, OverseerConnector, OverseerHandle},
polkadot_primitives::runtime_api::ParachainHost,
relay_chain_selection::SelectRelayChain,
sc_client_api::AuxStore,
sp_authority_discovery::AuthorityDiscoveryApi,
sp_blockchain::{HeaderBackend, HeaderMetadata},
sp_consensus_babe::BabeApi,
};
use std::{path::PathBuf, sync::Arc};
use prometheus_endpoint::Registry;
use sc_service::SpawnTaskHandle;
pub use chain_spec::{GenericChainSpec, RococoChainSpec, WestendChainSpec};
pub use polkadot_primitives::{Block, BlockId, BlockNumber, CollatorPair, Hash, Id as ParaId};
pub use sc_client_api::{Backend, CallExecutor};
pub use sc_consensus::{BlockImport, LongestChain};
pub use sc_executor::NativeExecutionDispatch;
use sc_executor::WasmExecutor;
pub use sc_service::{
config::{DatabaseSource, PrometheusConfig},
ChainSpec, Configuration, Error as SubstrateServiceError, PruningMode, Role, TFullBackend,
TFullCallExecutor, TFullClient, TaskManager, TransactionPoolOptions,
};
pub use sp_api::{ApiRef, ConstructRuntimeApi, Core as CoreApi, ProvideRuntimeApi};
pub use sp_consensus::{Proposal, SelectChain};
pub use sp_runtime::{
generic,
traits::{self as runtime_traits, BlakeTwo256, Block as BlockT, Header as HeaderT, NumberFor},
};
#[cfg(feature = "rococo-native")]
pub use {rococo_runtime, rococo_runtime_constants};
#[cfg(feature = "westend-native")]
pub use {westend_runtime, westend_runtime_constants};
pub use fake_runtime_api::{GetLastTimestamp, RuntimeApi};
#[cfg(feature = "full-node")]
pub type FullBackend = sc_service::TFullBackend<Block>;
#[cfg(feature = "full-node")]
pub type FullClient = sc_service::TFullClient<
Block,
RuntimeApi,
WasmExecutor<(sp_io::SubstrateHostFunctions, frame_benchmarking::benchmarking::HostFunctions)>,
>;
const GRANDPA_JUSTIFICATION_PERIOD: u32 = 512;
const KEEP_FINALIZED_FOR_LIVE_NETWORKS: u32 = 25;
pub trait HeaderProvider<Block, Error = sp_blockchain::Error>: Send + Sync + 'static
where
Block: BlockT,
Error: std::fmt::Debug + Send + Sync + 'static,
{
fn header(
&self,
hash: <Block as BlockT>::Hash,
) -> Result<Option<<Block as BlockT>::Header>, Error>;
fn number(
&self,
hash: <Block as BlockT>::Hash,
) -> Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>, Error>;
}
impl<Block, T> HeaderProvider<Block> for T
where
Block: BlockT,
T: sp_blockchain::HeaderBackend<Block> + 'static,
{
fn header(
&self,
hash: Block::Hash,
) -> sp_blockchain::Result<Option<<Block as BlockT>::Header>> {
<Self as sp_blockchain::HeaderBackend<Block>>::header(self, hash)
}
fn number(
&self,
hash: Block::Hash,
) -> sp_blockchain::Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>> {
<Self as sp_blockchain::HeaderBackend<Block>>::number(self, hash)
}
}
pub trait HeaderProviderProvider<Block>: Send + Sync + 'static
where
Block: BlockT,
{
type Provider: HeaderProvider<Block> + 'static;
fn header_provider(&self) -> &Self::Provider;
}
impl<Block, T> HeaderProviderProvider<Block> for T
where
Block: BlockT,
T: sc_client_api::Backend<Block> + 'static,
{
type Provider = <T as sc_client_api::Backend<Block>>::Blockchain;
fn header_provider(&self) -> &Self::Provider {
self.blockchain()
}
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
AddrFormatInvalid(#[from] std::net::AddrParseError),
#[error(transparent)]
Sub(#[from] SubstrateServiceError),
#[error(transparent)]
Blockchain(#[from] sp_blockchain::Error),
#[error(transparent)]
Consensus(#[from] sp_consensus::Error),
#[error("Failed to create an overseer")]
Overseer(#[from] polkadot_overseer::SubsystemError),
#[error(transparent)]
Prometheus(#[from] prometheus_endpoint::PrometheusError),
#[error(transparent)]
Telemetry(#[from] sc_telemetry::Error),
#[cfg(feature = "full-node")]
#[error(transparent)]
Availability(#[from] AvailabilityError),
#[error("Authorities require the real overseer implementation")]
AuthoritiesRequireRealOverseer,
#[cfg(feature = "full-node")]
#[error("Creating a custom database is required for validators")]
DatabasePathRequired,
#[cfg(feature = "full-node")]
#[error("Expected at least one of polkadot, kusama, westend or rococo runtime feature")]
NoRuntime,
#[cfg(feature = "full-node")]
#[error("Worker binaries not executable, prepare binary: {prep_worker_path:?}, execute binary: {exec_worker_path:?}")]
InvalidWorkerBinaries { prep_worker_path: PathBuf, exec_worker_path: PathBuf },
#[cfg(feature = "full-node")]
#[error("Worker binaries could not be found, make sure polkadot was built and installed correctly. Please see the readme for the latest instructions (https://github.com/paritytech/polkadot-sdk/tree/master/polkadot). If you ran with `cargo run`, please run `cargo build` first. Searched given workers path ({given_workers_path:?}), polkadot binary path ({current_exe_path:?}), and lib path (/usr/lib/polkadot), workers names: {workers_names:?}")]
MissingWorkerBinaries {
given_workers_path: Option<PathBuf>,
current_exe_path: PathBuf,
workers_names: Option<(String, String)>,
},
#[cfg(feature = "full-node")]
#[error("Version of worker binary ({worker_version}) is different from node version ({node_version}), worker_path: {worker_path}. If you ran with `cargo run`, please run `cargo build` first, otherwise try to `cargo clean`. TESTING ONLY: this check can be disabled with --disable-worker-version-check")]
WorkerBinaryVersionMismatch {
worker_version: String,
node_version: String,
worker_path: PathBuf,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Chain {
Polkadot,
Kusama,
Rococo,
Westend,
Unknown,
}
pub trait IdentifyVariant {
fn is_polkadot(&self) -> bool;
fn is_kusama(&self) -> bool;
fn is_westend(&self) -> bool;
fn is_rococo(&self) -> bool;
fn is_versi(&self) -> bool;
fn is_dev(&self) -> bool;
fn identify_chain(&self) -> Chain;
}
impl IdentifyVariant for Box<dyn ChainSpec> {
fn is_polkadot(&self) -> bool {
self.id().starts_with("polkadot") || self.id().starts_with("dot")
}
fn is_kusama(&self) -> bool {
self.id().starts_with("kusama") || self.id().starts_with("ksm")
}
fn is_westend(&self) -> bool {
self.id().starts_with("westend") || self.id().starts_with("wnd")
}
fn is_rococo(&self) -> bool {
self.id().starts_with("rococo") || self.id().starts_with("rco")
}
fn is_versi(&self) -> bool {
self.id().starts_with("versi") || self.id().starts_with("vrs")
}
fn is_dev(&self) -> bool {
self.id().ends_with("dev")
}
fn identify_chain(&self) -> Chain {
if self.is_polkadot() {
Chain::Polkadot
} else if self.is_kusama() {
Chain::Kusama
} else if self.is_westend() {
Chain::Westend
} else if self.is_rococo() || self.is_versi() {
Chain::Rococo
} else {
Chain::Unknown
}
}
}
pub trait IdentifyNetworkBackend {
fn network_backend(&self) -> sc_network::config::NetworkBackendType;
}
impl IdentifyNetworkBackend for Box<dyn ChainSpec> {
fn network_backend(&self) -> sc_network::config::NetworkBackendType {
if self.is_kusama() {
sc_network::config::NetworkBackendType::Litep2p
} else {
sc_network::config::NetworkBackendType::Libp2p
}
}
}
#[cfg(feature = "full-node")]
pub fn open_database(db_source: &DatabaseSource) -> Result<Arc<dyn Database>, Error> {
let parachains_db = match db_source {
DatabaseSource::RocksDb { path, .. } => parachains_db::open_creating_rocksdb(
path.clone(),
parachains_db::CacheSizes::default(),
)?,
DatabaseSource::ParityDb { path, .. } => parachains_db::open_creating_paritydb(
path.parent().ok_or(Error::DatabasePathRequired)?.into(),
parachains_db::CacheSizes::default(),
)?,
DatabaseSource::Auto { paritydb_path, rocksdb_path, .. } => {
if paritydb_path.is_dir() && paritydb_path.exists() {
parachains_db::open_creating_paritydb(
paritydb_path.parent().ok_or(Error::DatabasePathRequired)?.into(),
parachains_db::CacheSizes::default(),
)?
} else {
parachains_db::open_creating_rocksdb(
rocksdb_path.clone(),
parachains_db::CacheSizes::default(),
)?
}
},
DatabaseSource::Custom { .. } => {
unimplemented!("No polkadot subsystem db for custom source.");
},
};
Ok(parachains_db)
}
#[cfg(feature = "full-node")]
#[derive(Clone)]
pub enum IsParachainNode {
Collator(CollatorPair),
FullNode,
No,
}
#[cfg(feature = "full-node")]
impl std::fmt::Debug for IsParachainNode {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
use sp_core::Pair;
match self {
IsParachainNode::Collator(pair) => write!(fmt, "Collator({})", pair.public()),
IsParachainNode::FullNode => write!(fmt, "FullNode"),
IsParachainNode::No => write!(fmt, "No"),
}
}
}
#[cfg(feature = "full-node")]
impl IsParachainNode {
fn is_collator(&self) -> bool {
matches!(self, Self::Collator(_))
}
fn is_full_node(&self) -> bool {
matches!(self, Self::FullNode)
}
fn is_running_alongside_parachain_node(&self) -> bool {
self.is_collator() || self.is_full_node()
}
}
#[cfg(feature = "full-node")]
macro_rules! chain_ops {
($config:expr, $telemetry_worker_handle:expr) => {{
let telemetry_worker_handle = $telemetry_worker_handle;
let mut config = $config;
let basics = new_partial_basics(config, telemetry_worker_handle)?;
use ::sc_consensus::LongestChain;
let chain_selection = LongestChain::new(basics.backend.clone());
let sc_service::PartialComponents { client, backend, import_queue, task_manager, .. } =
new_partial::<LongestChain<_, Block>>(&mut config, basics, chain_selection)?;
Ok((client, backend, import_queue, task_manager))
}};
}
#[cfg(feature = "full-node")]
pub fn new_chain_ops(
config: &mut Configuration,
) -> Result<(Arc<FullClient>, Arc<FullBackend>, sc_consensus::BasicQueue<Block>, TaskManager), Error>
{
config.keystore = sc_service::config::KeystoreConfig::InMemory;
if config.chain_spec.is_rococo() || config.chain_spec.is_versi() {
chain_ops!(config, None)
} else if config.chain_spec.is_kusama() {
chain_ops!(config, None)
} else if config.chain_spec.is_westend() {
return chain_ops!(config, None);
} else {
chain_ops!(config, None)
}
}
#[cfg(feature = "full-node")]
pub fn build_full<OverseerGenerator: OverseerGen>(
config: Configuration,
mut params: NewFullParams<OverseerGenerator>,
) -> Result<NewFull, Error> {
let is_polkadot = config.chain_spec.is_polkadot();
params.overseer_message_channel_capacity_override =
params.overseer_message_channel_capacity_override.map(move |capacity| {
if is_polkadot {
gum::warn!("Channel capacity should _never_ be tampered with on polkadot!");
}
capacity
});
let default_backend = config.chain_spec.network_backend();
let network_backend = config.network.network_backend.unwrap_or(default_backend);
match network_backend {
sc_network::config::NetworkBackendType::Libp2p =>
new_full::<_, sc_network::NetworkWorker<Block, Hash>>(config, params),
sc_network::config::NetworkBackendType::Litep2p =>
new_full::<_, sc_network::Litep2pNetworkBackend>(config, params),
}
}
#[cfg(feature = "full-node")]
pub fn revert_backend(
client: Arc<FullClient>,
backend: Arc<FullBackend>,
blocks: BlockNumber,
config: Configuration,
task_handle: SpawnTaskHandle,
) -> Result<(), Error> {
let best_number = client.info().best_number;
let finalized = client.info().finalized_number;
let revertible = blocks.min(best_number - finalized);
if revertible == 0 {
return Ok(());
}
let number = best_number - revertible;
let hash = client.block_hash_from_id(&BlockId::Number(number))?.ok_or(
sp_blockchain::Error::Backend(format!(
"Unexpected hash lookup failure for block number: {}",
number
)),
)?;
let parachains_db = open_database(&config.database)
.map_err(|err| sp_blockchain::Error::Backend(err.to_string()))?;
revert_approval_voting(parachains_db.clone(), hash, task_handle)?;
revert_chain_selection(parachains_db, hash)?;
sc_consensus_babe::revert(client.clone(), backend, blocks)?;
sc_consensus_grandpa::revert(client, blocks)?;
Ok(())
}
fn revert_chain_selection(db: Arc<dyn Database>, hash: Hash) -> sp_blockchain::Result<()> {
let config = chain_selection_subsystem::Config {
col_data: parachains_db::REAL_COLUMNS.col_chain_selection_data,
stagnant_check_interval: chain_selection_subsystem::StagnantCheckInterval::never(),
stagnant_check_mode: chain_selection_subsystem::StagnantCheckMode::PruneOnly,
};
let chain_selection = chain_selection_subsystem::ChainSelectionSubsystem::new(config, db);
chain_selection
.revert_to(hash)
.map_err(|err| sp_blockchain::Error::Backend(err.to_string()))
}
fn revert_approval_voting(
db: Arc<dyn Database>,
hash: Hash,
task_handle: SpawnTaskHandle,
) -> sp_blockchain::Result<()> {
let config = approval_voting_subsystem::Config {
col_approval_data: parachains_db::REAL_COLUMNS.col_approval_data,
slot_duration_millis: Default::default(),
};
let approval_voting = approval_voting_subsystem::ApprovalVotingSubsystem::with_config(
config,
db,
Arc::new(sc_keystore::LocalKeystore::in_memory()),
Box::new(sp_consensus::NoNetwork),
approval_voting_subsystem::Metrics::default(),
Arc::new(SpawnGlue(task_handle)),
);
approval_voting
.revert_to(hash)
.map_err(|err| sp_blockchain::Error::Backend(err.to_string()))
}