feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit e4778b4576
6838 changed files with 1847450 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
[package]
name = "sc-cli"
version = "0.36.0"
authors.workspace = true
description = "Substrate CLI interface."
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
homepage.workspace = true
repository.workspace = true
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
array-bytes = { workspace = true, default-features = true }
bip39 = { workspace = true, default-features = true, features = ["rand"] }
chrono = { workspace = true }
clap = { features = ["derive", "string", "wrap_help"], workspace = true }
codec = { workspace = true, default-features = true }
fdlimit = { workspace = true }
futures = { workspace = true }
itertools = { workspace = true }
libp2p-identity = { features = ["ed25519", "peerid"], workspace = true }
log = { workspace = true, default-features = true }
names = { workspace = true }
rand = { workspace = true, default-features = true }
regex = { workspace = true }
rpassword = { workspace = true }
sc-client-api = { workspace = true, default-features = true }
sc-client-db = { workspace = true, default-features = false }
sc-keystore = { workspace = true, default-features = true }
sc-mixnet = { workspace = true, default-features = true }
sc-network = { workspace = true, default-features = true }
sc-service = { workspace = true, default-features = false }
sc-telemetry = { workspace = true, default-features = true }
sc-tracing = { workspace = true, default-features = true }
sc-transaction-pool = { workspace = true, default-features = true }
sc-utils = { workspace = true, default-features = true }
serde = { workspace = true, default-features = true }
serde_json = { workspace = true, default-features = true }
sp-blockchain = { workspace = true, default-features = true }
sp-core = { workspace = true, default-features = true }
sp-keyring = { workspace = true, default-features = true }
sp-keystore = { workspace = true, default-features = true }
sp-panic-handler = { workspace = true, default-features = true }
sp-runtime = { workspace = true, default-features = true }
sp-version = { workspace = true, default-features = true }
thiserror = { workspace = true }
tokio = { features = [
"parking_lot",
"rt-multi-thread",
"signal",
], workspace = true, default-features = true }
[dev-dependencies]
futures-timer = { workspace = true }
sp-tracing = { workspace = true, default-features = true }
tempfile = { workspace = true }
[features]
default = ["rocksdb"]
rocksdb = ["sc-client-db/rocksdb"]
runtime-benchmarks = [
"sc-client-api/runtime-benchmarks",
"sc-client-db/runtime-benchmarks",
"sc-mixnet/runtime-benchmarks",
"sc-network/runtime-benchmarks",
"sc-service/runtime-benchmarks",
"sc-tracing/runtime-benchmarks",
"sc-transaction-pool/runtime-benchmarks",
"sp-blockchain/runtime-benchmarks",
"sp-keyring/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"sp-version/runtime-benchmarks",
]
+3
View File
@@ -0,0 +1,3 @@
Substrate CLI library.
License: GPL-3.0-or-later WITH Classpath-exception-2.0
+331
View File
@@ -0,0 +1,331 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Definitions of [`ValueEnum`] types.
use clap::ValueEnum;
use std::str::FromStr;
/// The instantiation strategy to use in compiled mode.
#[derive(Debug, Clone, Copy, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum WasmtimeInstantiationStrategy {
/// Pool the instances to avoid initializing everything from scratch
/// on each instantiation. Use copy-on-write memory when possible.
PoolingCopyOnWrite,
/// Recreate the instance from scratch on every instantiation.
/// Use copy-on-write memory when possible.
RecreateInstanceCopyOnWrite,
/// Pool the instances to avoid initializing everything from scratch
/// on each instantiation.
Pooling,
/// Recreate the instance from scratch on every instantiation. Very slow.
RecreateInstance,
}
/// The default [`WasmtimeInstantiationStrategy`].
pub const DEFAULT_WASMTIME_INSTANTIATION_STRATEGY: WasmtimeInstantiationStrategy =
WasmtimeInstantiationStrategy::PoolingCopyOnWrite;
/// How to execute Wasm runtime code.
#[derive(Debug, Clone, Copy, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum WasmExecutionMethod {
/// Uses an interpreter which now is deprecated.
#[clap(name = "interpreted-i-know-what-i-do")]
Interpreted,
/// Uses a compiled runtime.
Compiled,
}
impl std::fmt::Display for WasmExecutionMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Interpreted => write!(f, "Interpreted"),
Self::Compiled => write!(f, "Compiled"),
}
}
}
/// Converts the execution method and instantiation strategy command line arguments
/// into an execution method which can be used internally.
pub fn execution_method_from_cli(
execution_method: WasmExecutionMethod,
instantiation_strategy: WasmtimeInstantiationStrategy,
) -> sc_service::config::WasmExecutionMethod {
if let WasmExecutionMethod::Interpreted = execution_method {
log::warn!(
"`interpreted-i-know-what-i-do` is deprecated and will be removed in the future. Defaults to `compiled` execution mode."
);
}
sc_service::config::WasmExecutionMethod::Compiled {
instantiation_strategy: match instantiation_strategy {
WasmtimeInstantiationStrategy::PoolingCopyOnWrite =>
sc_service::config::WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite =>
sc_service::config::WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite,
WasmtimeInstantiationStrategy::Pooling =>
sc_service::config::WasmtimeInstantiationStrategy::Pooling,
WasmtimeInstantiationStrategy::RecreateInstance =>
sc_service::config::WasmtimeInstantiationStrategy::RecreateInstance,
},
}
}
/// The default [`WasmExecutionMethod`].
pub const DEFAULT_WASM_EXECUTION_METHOD: WasmExecutionMethod = WasmExecutionMethod::Compiled;
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum TracingReceiver {
/// Output the tracing records using the log.
Log,
}
impl Into<sc_tracing::TracingReceiver> for TracingReceiver {
fn into(self) -> sc_tracing::TracingReceiver {
match self {
TracingReceiver::Log => sc_tracing::TracingReceiver::Log,
}
}
}
/// The type of the node key.
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum NodeKeyType {
/// Use ed25519.
Ed25519,
}
/// The crypto scheme to use.
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum CryptoScheme {
/// Use ed25519.
Ed25519,
/// Use sr25519.
Sr25519,
/// Use ecdsa.
Ecdsa,
}
/// The type of the output format.
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum OutputType {
/// Output as json.
Json,
/// Output as text.
Text,
}
/// How to execute blocks
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum ExecutionStrategy {
/// Execute with native build (if available, WebAssembly otherwise).
Native,
/// Only execute with the WebAssembly build.
Wasm,
/// Execute with both native (where available) and WebAssembly builds.
Both,
/// Execute with the native build if possible; if it fails, then execute with WebAssembly.
NativeElseWasm,
}
/// Available RPC methods.
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone, PartialEq, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum RpcMethods {
/// Expose every RPC method only when RPC is listening on `localhost`,
/// otherwise serve only safe RPC methods.
Auto,
/// Allow only a safe subset of RPC methods.
Safe,
/// Expose every RPC method (even potentially unsafe ones).
Unsafe,
}
impl FromStr for RpcMethods {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"safe" => Ok(RpcMethods::Safe),
"unsafe" => Ok(RpcMethods::Unsafe),
"auto" => Ok(RpcMethods::Auto),
invalid => Err(format!("Invalid rpc methods {invalid}")),
}
}
}
impl Into<sc_service::config::RpcMethods> for RpcMethods {
fn into(self) -> sc_service::config::RpcMethods {
match self {
RpcMethods::Auto => sc_service::config::RpcMethods::Auto,
RpcMethods::Safe => sc_service::config::RpcMethods::Safe,
RpcMethods::Unsafe => sc_service::config::RpcMethods::Unsafe,
}
}
}
/// CORS setting
///
/// The type is introduced to overcome `Option<Option<T>>` handling of `clap`.
#[derive(Clone, Debug)]
pub enum Cors {
/// All hosts allowed.
All,
/// Only hosts on the list are allowed.
List(Vec<String>),
}
impl From<Cors> for Option<Vec<String>> {
fn from(cors: Cors) -> Self {
match cors {
Cors::All => None,
Cors::List(list) => Some(list),
}
}
}
impl FromStr for Cors {
type Err = crate::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut is_all = false;
let mut origins = Vec::new();
for part in s.split(',') {
match part {
"all" | "*" => {
is_all = true;
break;
},
other => origins.push(other.to_owned()),
}
}
if is_all {
Ok(Cors::All)
} else {
Ok(Cors::List(origins))
}
}
}
/// Database backend
#[derive(Debug, Clone, PartialEq, Copy, clap::ValueEnum)]
#[value(rename_all = "lower")]
pub enum Database {
/// Facebooks RocksDB
#[cfg(feature = "rocksdb")]
RocksDb,
/// ParityDb. <https://github.com/paritytech/parity-db/>
ParityDb,
/// Detect whether there is an existing database. Use it, if there is, if not, create new
/// instance of ParityDb
Auto,
/// ParityDb. <https://github.com/paritytech/parity-db/>
#[value(name = "paritydb-experimental")]
ParityDbDeprecated,
}
impl Database {
/// Returns all the variants of this enum to be shown in the cli.
pub const fn variants() -> &'static [&'static str] {
&[
#[cfg(feature = "rocksdb")]
"rocksdb",
"paritydb",
"paritydb-experimental",
"auto",
]
}
}
/// Whether off-chain workers are enabled.
#[allow(missing_docs)]
#[derive(Debug, Clone, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum OffchainWorkerEnabled {
/// Always have offchain worker enabled.
Always,
/// Never enable the offchain worker.
Never,
/// Only enable the offchain worker when running as a validator (or collator, if this is a
/// teyrchain node).
WhenAuthority,
}
/// Syncing mode.
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq)]
#[value(rename_all = "kebab-case")]
pub enum SyncMode {
/// Full sync. Download and verify all blocks.
Full,
/// Download blocks without executing them. Download latest state with proofs.
Fast,
/// Download blocks without executing them. Download latest state without proofs.
FastUnsafe,
/// Prove finality and download the latest state.
Warp,
}
impl Into<sc_network::config::SyncMode> for SyncMode {
fn into(self) -> sc_network::config::SyncMode {
match self {
SyncMode::Full => sc_network::config::SyncMode::Full,
SyncMode::Fast => sc_network::config::SyncMode::LightState {
skip_proofs: false,
storage_chain_mode: false,
},
SyncMode::FastUnsafe => sc_network::config::SyncMode::LightState {
skip_proofs: true,
storage_chain_mode: false,
},
SyncMode::Warp => sc_network::config::SyncMode::Warp,
}
}
}
/// Network backend type.
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq)]
#[value(rename_all = "lower")]
pub enum NetworkBackendType {
/// Use libp2p for P2P networking.
Libp2p,
/// Use litep2p for P2P networking.
Litep2p,
}
impl Into<sc_network::config::NetworkBackendType> for NetworkBackendType {
fn into(self) -> sc_network::config::NetworkBackendType {
match self {
Self::Libp2p => sc_network::config::NetworkBackendType::Libp2p,
Self::Litep2p => sc_network::config::NetworkBackendType::Litep2p,
}
}
}
@@ -0,0 +1,91 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{NodeKeyParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use log::info;
use sc_network::config::build_multiaddr;
use sc_service::{
config::{MultiaddrWithPeerId, NetworkConfiguration},
ChainSpec,
};
use std::io::Write;
/// The `build-spec` command used to build a specification.
#[derive(Debug, Clone, Parser)]
pub struct BuildSpecCmd {
/// Force raw genesis storage output.
#[arg(long)]
pub raw: bool,
/// Disable adding the default bootnode to the specification.
/// By default the `/ip4/127.0.0.1/tcp/30333/p2p/NODE_PEER_ID` bootnode is added to the
/// specification when no bootnode exists.
#[arg(long)]
pub disable_default_bootnode: bool,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub node_key_params: NodeKeyParams,
}
impl BuildSpecCmd {
/// Run the build-spec command
pub fn run(
&self,
mut spec: Box<dyn ChainSpec>,
network_config: NetworkConfiguration,
) -> error::Result<()> {
info!("Building chain spec");
let raw_output = self.raw;
if spec.boot_nodes().is_empty() && !self.disable_default_bootnode {
let keys = network_config.node_key.into_keypair()?;
let peer_id = keys.public().to_peer_id();
let addr = MultiaddrWithPeerId {
multiaddr: build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(30333u16)],
peer_id: peer_id.into(),
};
spec.add_boot_node(addr)
}
let json = sc_service::chain_ops::build_spec(&*spec, raw_output)?;
if std::io::stdout().write_all(json.as_bytes()).is_err() {
let _ = std::io::stderr().write_all(b"Error writing to stdout\n");
}
Ok(())
}
}
impl CliConfiguration for BuildSpecCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn node_key_params(&self) -> Option<&NodeKeyParams> {
Some(&self.node_key_params)
}
}
@@ -0,0 +1,103 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{CliConfiguration, DatabaseParams, PruningParams, Result as CliResult, SharedParams};
use codec::{Decode, Encode};
use sc_client_api::{backend::Backend as BackendT, blockchain::HeaderBackend};
use sp_blockchain::Info;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
use std::{fmt::Debug, io};
/// The `chain-info` subcommand used to output db meta columns information.
#[derive(Debug, Clone, clap::Parser)]
pub struct ChainInfoCmd {
#[allow(missing_docs)]
#[clap(flatten)]
pub pruning_params: PruningParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub database_params: DatabaseParams,
}
/// Serializable `chain-info` subcommand output.
#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, serde::Serialize)]
struct ChainInfo<B: BlockT> {
/// Best block hash.
best_hash: B::Hash,
/// Best block number.
best_number: <<B as BlockT>::Header as HeaderT>::Number,
/// Genesis block hash.
genesis_hash: B::Hash,
/// The head of the finalized chain.
finalized_hash: B::Hash,
/// Last finalized block number.
finalized_number: <<B as BlockT>::Header as HeaderT>::Number,
}
impl<B: BlockT> From<Info<B>> for ChainInfo<B> {
fn from(info: Info<B>) -> Self {
ChainInfo::<B> {
best_hash: info.best_hash,
best_number: info.best_number,
genesis_hash: info.genesis_hash,
finalized_hash: info.finalized_hash,
finalized_number: info.finalized_number,
}
}
}
impl ChainInfoCmd {
/// Run the `chain-info` subcommand
pub fn run<B>(&self, config: &sc_service::Configuration) -> CliResult<()>
where
B: BlockT,
{
let db_config = sc_client_db::DatabaseSettings {
trie_cache_maximum_size: config.trie_cache_maximum_size,
state_pruning: config.state_pruning.clone(),
source: config.database.clone(),
blocks_pruning: config.blocks_pruning,
metrics_registry: None,
};
let backend = sc_service::new_db_backend::<B>(db_config)?;
let info: ChainInfo<B> = backend.blockchain().info().into();
let mut out = io::stdout();
serde_json::to_writer_pretty(&mut out, &info)
.map_err(|e| format!("Error writing JSON: {}", e))?;
Ok(())
}
}
impl CliConfiguration for ChainInfoCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn pruning_params(&self) -> Option<&PruningParams> {
Some(&self.pruning_params)
}
fn database_params(&self) -> Option<&DatabaseParams> {
Some(&self.database_params)
}
}
@@ -0,0 +1,76 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{BlockNumberOrHash, ImportParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use sc_client_api::{BlockBackend, HeaderBackend};
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
use std::{fmt::Debug, str::FromStr, sync::Arc};
/// The `check-block` command used to validate blocks.
#[derive(Debug, Clone, Parser)]
pub struct CheckBlockCmd {
/// Block hash or number.
#[arg(value_name = "HASH or NUMBER")]
pub input: BlockNumberOrHash,
/// The default number of 64KB pages to ever allocate for Wasm execution.
/// Don't alter this unless you know what you're doing.
#[arg(long, value_name = "COUNT")]
pub default_heap_pages: Option<u32>,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub import_params: ImportParams,
}
impl CheckBlockCmd {
/// Run the check-block command
pub async fn run<B, C, IQ>(&self, client: Arc<C>, import_queue: IQ) -> error::Result<()>
where
B: BlockT + for<'de> serde::Deserialize<'de>,
C: BlockBackend<B> + HeaderBackend<B> + Send + Sync + 'static,
IQ: sc_service::ImportQueue<B> + 'static,
<B::Hash as FromStr>::Err: Debug,
<<B::Header as HeaderT>::Number as FromStr>::Err: Debug,
{
let start = std::time::Instant::now();
sc_service::chain_ops::check_block(client, import_queue, self.input.parse()?).await?;
println!("Completed in {} ms.", start.elapsed().as_millis());
Ok(())
}
}
impl CliConfiguration for CheckBlockCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn import_params(&self) -> Option<&ImportParams> {
Some(&self.import_params)
}
}
@@ -0,0 +1,107 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{DatabaseParams, GenericNumber, PruningParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use log::info;
use sc_client_api::{BlockBackend, HeaderBackend, UsageProvider};
use sc_service::{chain_ops::export_blocks, config::DatabaseSource};
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
use std::{fmt::Debug, fs, io, path::PathBuf, str::FromStr, sync::Arc};
/// The `export-blocks` command used to export blocks.
#[derive(Debug, Clone, Parser)]
pub struct ExportBlocksCmd {
/// Output file name or stdout if unspecified.
#[arg()]
pub output: Option<PathBuf>,
/// Specify starting block number.
/// Default is 1.
#[arg(long, value_name = "BLOCK")]
pub from: Option<GenericNumber>,
/// Specify last block number.
/// Default is best block.
#[arg(long, value_name = "BLOCK")]
pub to: Option<GenericNumber>,
/// Use binary output rather than JSON.
#[arg(long)]
pub binary: bool,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub pruning_params: PruningParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub database_params: DatabaseParams,
}
impl ExportBlocksCmd {
/// Run the export-blocks command
pub async fn run<B, C>(
&self,
client: Arc<C>,
database_config: DatabaseSource,
) -> error::Result<()>
where
B: BlockT,
C: HeaderBackend<B> + BlockBackend<B> + UsageProvider<B> + 'static,
<<B::Header as HeaderT>::Number as FromStr>::Err: Debug,
{
if let Some(path) = database_config.path() {
info!("DB path: {}", path.display());
}
let from = self.from.as_ref().and_then(|f| f.parse().ok()).unwrap_or(1u32);
let to = self.to.as_ref().and_then(|t| t.parse().ok());
let binary = self.binary;
let file: Box<dyn io::Write> = match &self.output {
Some(filename) => Box::new(fs::File::create(filename)?),
None => Box::new(io::stdout()),
};
export_blocks(client, file, from.into(), to, binary).await.map_err(Into::into)
}
}
impl CliConfiguration for ExportBlocksCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn pruning_params(&self) -> Option<&PruningParams> {
Some(&self.pruning_params)
}
fn database_params(&self) -> Option<&DatabaseParams> {
Some(&self.database_params)
}
}
@@ -0,0 +1,68 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::error::Result;
use clap::Parser;
use sc_service::{chain_ops, ChainSpec};
use std::{
fs,
io::{self, Write},
path::PathBuf,
};
/// Export a chain-spec to a JSON file in plain or in raw storage format.
///
/// Nodes that expose this command usually have embedded runtimes WASM blobs with
/// genesis config presets which can be referenced via `--chain <id>` . The logic for
/// loading the chain spec into memory based on an `id` is specific to each
/// node and is a prerequisite to enable this command.
///
/// Same functionality can be achieved currently via
/// [`crate::commands::build_spec_cmd::BuildSpecCmd`] but we recommend
/// `export-chain-spec` in its stead. `build-spec` is known
/// to be a legacy mix of exporting chain specs to JSON files or
/// converting them to raw, which will be better
/// represented under `export-chain-spec`.
#[derive(Debug, Clone, Parser)]
pub struct ExportChainSpecCmd {
/// The chain spec identifier to export.
#[arg(long, default_value = "local")]
pub chain: String,
/// `chain-spec` JSON file path. If omitted, prints to stdout.
#[arg(long)]
pub output: Option<PathBuf>,
/// Export in raw genesis storage format.
#[arg(long)]
pub raw: bool,
}
impl ExportChainSpecCmd {
/// Run the export-chain-spec command
pub fn run(&self, spec: Box<dyn ChainSpec>) -> Result<()> {
let json = chain_ops::build_spec(spec.as_ref(), self.raw)?;
if let Some(ref path) = self.output {
fs::write(path, json)?;
println!("Exported chain spec to {}", path.display());
} else {
io::stdout().write_all(json.as_bytes()).map_err(|e| format!("{}", e))?;
}
Ok(())
}
}
@@ -0,0 +1,95 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{BlockNumberOrHash, DatabaseParams, PruningParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use log::info;
use sc_client_api::{HeaderBackend, StorageProvider, UsageProvider};
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
use std::{fmt::Debug, io::Write, str::FromStr, sync::Arc};
/// The `export-state` command used to export the state of a given block into
/// a chain spec.
#[derive(Debug, Clone, Parser)]
pub struct ExportStateCmd {
/// Block hash or number.
#[arg(value_name = "HASH or NUMBER")]
pub input: Option<BlockNumberOrHash>,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub pruning_params: PruningParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub database_params: DatabaseParams,
}
impl ExportStateCmd {
/// Run the `export-state` command
pub async fn run<B, BA, C>(
&self,
client: Arc<C>,
mut input_spec: Box<dyn sc_service::ChainSpec>,
) -> error::Result<()>
where
B: BlockT,
C: UsageProvider<B> + StorageProvider<B, BA> + HeaderBackend<B>,
BA: sc_client_api::backend::Backend<B>,
<B::Hash as FromStr>::Err: Debug,
<<B::Header as HeaderT>::Number as FromStr>::Err: Debug,
{
info!("Exporting raw state...");
let block_id = self.input.as_ref().map(|b| b.parse()).transpose()?;
let hash = match block_id {
Some(id) => client.expect_block_hash_from_id(&id)?,
None => client.usage_info().chain.best_hash,
};
let raw_state = sc_service::chain_ops::export_raw_state(client, hash)?;
input_spec.set_storage(raw_state);
info!("Generating new chain spec...");
let json = sc_service::chain_ops::build_spec(&*input_spec, true)?;
if std::io::stdout().write_all(json.as_bytes()).is_err() {
let _ = std::io::stderr().write_all(b"Error writing to stdout\n");
}
Ok(())
}
}
impl CliConfiguration for ExportStateCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn pruning_params(&self) -> Option<&PruningParams> {
Some(&self.pruning_params)
}
fn database_params(&self) -> Option<&DatabaseParams> {
Some(&self.database_params)
}
}
@@ -0,0 +1,87 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the `generate` subcommand
use crate::{
utils::print_from_uri, with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams,
NetworkSchemeFlag, OutputTypeFlag,
};
use bip39::Mnemonic;
use clap::Parser;
use itertools::Itertools;
/// The `generate` command
#[derive(Debug, Clone, Parser)]
#[command(name = "generate", about = "Generate a random account")]
pub struct GenerateCmd {
/// The number of words in the phrase to generate. One of 12 (default), 15, 18, 21 and 24.
#[arg(short = 'w', long, value_name = "WORDS")]
words: Option<usize>,
#[allow(missing_docs)]
#[clap(flatten)]
pub keystore_params: KeystoreParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub network_scheme: NetworkSchemeFlag,
#[allow(missing_docs)]
#[clap(flatten)]
pub output_scheme: OutputTypeFlag,
#[allow(missing_docs)]
#[clap(flatten)]
pub crypto_scheme: CryptoSchemeFlag,
}
impl GenerateCmd {
/// Run the command
pub fn run(&self) -> Result<(), Error> {
let words = match self.words {
Some(words_count) if [12, 15, 18, 21, 24].contains(&words_count) => Ok(words_count),
Some(_) => Err(Error::Input(
"Invalid number of words given for phrase: must be 12/15/18/21/24".into(),
)),
None => Ok(12),
}?;
let mnemonic = Mnemonic::generate(words)
.map_err(|e| Error::Input(format!("Mnemonic generation failed: {e}").into()))?;
let password = self.keystore_params.read_password()?;
let output = self.output_scheme.output_type;
let phrase = mnemonic.words().join(" ");
with_crypto_scheme!(
self.crypto_scheme.scheme,
print_from_uri(&phrase, password, self.network_scheme.network, output)
);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate() {
let generate = GenerateCmd::parse_from(&["generate", "--password", "12345"]);
assert!(generate.run().is_ok())
}
}
@@ -0,0 +1,187 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the `generate-node-key` subcommand
use crate::{build_network_key_dir_or_default, Error, NODE_KEY_ED25519_FILE};
use clap::{Args, Parser};
use libp2p_identity::{ed25519, Keypair};
use sc_service::BasePath;
use std::{
fs,
io::{self, Write},
path::PathBuf,
};
/// Common arguments accross all generate key commands, subkey and node.
#[derive(Debug, Args, Clone)]
pub struct GenerateKeyCmdCommon {
/// Name of file to save secret key to.
/// If not given, the secret key is printed to stdout.
#[arg(long)]
file: Option<PathBuf>,
/// The output is in raw binary format.
/// If not given, the output is written as an hex encoded string.
#[arg(long)]
bin: bool,
}
/// The `generate-node-key` command
#[derive(Debug, Clone, Parser)]
#[command(
name = "generate-node-key",
about = "Generate a random node key, write it to a file or stdout \
and write the corresponding peer-id to stderr"
)]
pub struct GenerateNodeKeyCmd {
#[clap(flatten)]
pub common: GenerateKeyCmdCommon,
/// Specify the chain specification.
///
/// It can be any of the predefined chains like dev, local, staging, pezkuwi, kusama.
#[arg(long, value_name = "CHAIN_SPEC")]
pub chain: Option<String>,
/// A directory where the key should be saved. If a key already
/// exists in the directory, it won't be overwritten.
#[arg(long, conflicts_with_all = ["file", "default_base_path"])]
base_path: Option<PathBuf>,
/// Save the key in the default directory. If a key already
/// exists in the directory, it won't be overwritten.
#[arg(long, conflicts_with_all = ["base_path", "file"])]
default_base_path: bool,
}
impl GenerateKeyCmdCommon {
/// Run the command
pub fn run(&self) -> Result<(), Error> {
generate_key(&self.file, self.bin, None, &None, false, None)
}
}
impl GenerateNodeKeyCmd {
/// Run the command
pub fn run(&self, chain_spec_id: &str, executable_name: &String) -> Result<(), Error> {
generate_key(
&self.common.file,
self.common.bin,
Some(chain_spec_id),
&self.base_path,
self.default_base_path,
Some(executable_name),
)
}
}
// Utility function for generating a key based on the provided CLI arguments
//
// `file` - Name of file to save secret key to
// `bin`
fn generate_key(
file: &Option<PathBuf>,
bin: bool,
chain_spec_id: Option<&str>,
base_path: &Option<PathBuf>,
default_base_path: bool,
executable_name: Option<&String>,
) -> Result<(), Error> {
let keypair = ed25519::Keypair::generate();
let secret = keypair.secret();
let file_data = if bin {
secret.as_ref().to_owned()
} else {
array_bytes::bytes2hex("", secret).into_bytes()
};
match (file, base_path, default_base_path) {
(Some(file), None, false) => fs::write(file, file_data)?,
(None, Some(_), false) | (None, None, true) => {
let network_path = build_network_key_dir_or_default(
base_path.clone().map(BasePath::new),
chain_spec_id.unwrap_or_default(),
executable_name.ok_or(Error::Input("Executable name not provided".into()))?,
);
fs::create_dir_all(network_path.as_path())?;
let key_path = network_path.join(NODE_KEY_ED25519_FILE);
if key_path.exists() {
eprintln!("Skip generation, a key already exists in {:?}", key_path);
return Err(Error::KeyAlreadyExistsInPath(key_path));
} else {
eprintln!("Generating key in {:?}", key_path);
fs::write(key_path, file_data)?
}
},
(None, None, false) => io::stdout().lock().write_all(&file_data)?,
(_, _, _) => {
// This should not happen, arguments are marked as mutually exclusive.
return Err(Error::Input("Mutually exclusive arguments provided".into()));
},
}
eprintln!("{}", Keypair::from(keypair).public().to_peer_id());
Ok(())
}
#[cfg(test)]
pub mod tests {
use crate::DEFAULT_NETWORK_CONFIG_PATH;
use super::*;
use std::io::Read;
use tempfile::Builder;
#[test]
fn generate_node_key() {
let mut file = Builder::new().prefix("keyfile").tempfile().unwrap();
let file_path = file.path().display().to_string();
let generate = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", &file_path]);
assert!(generate.run("test", &String::from("test")).is_ok());
let mut buf = String::new();
assert!(file.read_to_string(&mut buf).is_ok());
assert!(array_bytes::hex2bytes(&buf).is_ok());
}
#[test]
fn generate_node_key_base_path() {
let base_dir = Builder::new().prefix("keyfile").tempdir().unwrap();
let key_path = base_dir
.path()
.join("chains/test_id/")
.join(DEFAULT_NETWORK_CONFIG_PATH)
.join(NODE_KEY_ED25519_FILE);
let base_path = base_dir.path().display().to_string();
let generate =
GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--base-path", &base_path]);
assert!(generate.run("test_id", &String::from("test")).is_ok());
let buf = fs::read_to_string(key_path.as_path()).unwrap();
assert!(array_bytes::hex2bytes(&buf).is_ok());
assert!(generate.run("test_id", &String::from("test")).is_err());
let new_buf = fs::read_to_string(key_path).unwrap();
assert_eq!(
array_bytes::hex2bytes(&new_buf).unwrap(),
array_bytes::hex2bytes(&buf).unwrap()
);
}
}
@@ -0,0 +1,88 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{ImportParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use sc_client_api::HeaderBackend;
use sc_service::chain_ops::import_blocks;
use sp_runtime::traits::Block as BlockT;
use std::{
fmt::Debug,
fs,
io::{self, Read},
path::PathBuf,
sync::Arc,
};
/// The `import-blocks` command used to import blocks.
#[derive(Debug, Parser)]
pub struct ImportBlocksCmd {
/// Input file or stdin if unspecified.
#[arg()]
pub input: Option<PathBuf>,
/// The default number of 64KB pages to ever allocate for Wasm execution.
/// Don't alter this unless you know what you're doing.
#[arg(long, value_name = "COUNT")]
pub default_heap_pages: Option<u32>,
/// Try importing blocks from binary format rather than JSON.
#[arg(long)]
pub binary: bool,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub import_params: ImportParams,
}
impl ImportBlocksCmd {
/// Run the import-blocks command
pub async fn run<B, C, IQ>(&self, client: Arc<C>, import_queue: IQ) -> error::Result<()>
where
C: HeaderBackend<B> + Send + Sync + 'static,
B: BlockT + for<'de> serde::Deserialize<'de>,
IQ: sc_service::ImportQueue<B> + 'static,
{
let file: Box<dyn Read + Send> = match &self.input {
Some(filename) => Box::new(fs::File::open(filename)?),
None => Box::new(io::stdin()),
};
import_blocks(client, import_queue, file, false, self.binary)
.await
.map_err(Into::into)
}
}
impl CliConfiguration for ImportBlocksCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn import_params(&self) -> Option<&ImportParams> {
Some(&self.import_params)
}
}
@@ -0,0 +1,166 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the `insert` subcommand
use crate::{
utils, with_crypto_scheme, CryptoScheme, Error, KeystoreParams, SharedParams, SubstrateCli,
};
use clap::Parser;
use sc_keystore::LocalKeystore;
use sc_service::config::{BasePath, KeystoreConfig};
use sp_core::crypto::{KeyTypeId, SecretString};
use sp_keystore::KeystorePtr;
/// The `insert` command
#[derive(Debug, Clone, Parser)]
#[command(name = "insert", about = "Insert a key to the keystore of a node.")]
pub struct InsertKeyCmd {
/// The secret key URI.
/// If the value is a file, the file content is used as URI.
/// If not given, you will be prompted for the URI.
#[arg(long)]
suri: Option<String>,
/// Key type, examples: "gran", or "imon".
#[arg(long)]
key_type: String,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub keystore_params: KeystoreParams,
/// The cryptography scheme that should be used to generate the key out of the given URI.
#[arg(long, value_name = "SCHEME", value_enum, ignore_case = true)]
pub scheme: CryptoScheme,
}
impl InsertKeyCmd {
/// Run the command
pub fn run<C: SubstrateCli>(&self, cli: &C) -> Result<(), Error> {
let suri = utils::read_uri(self.suri.as_ref())?;
let base_path = self
.shared_params
.base_path()?
.unwrap_or_else(|| BasePath::from_project("", "", &C::executable_name()));
let chain_id = self.shared_params.chain_id(self.shared_params.is_dev());
let chain_spec = cli.load_spec(&chain_id)?;
let config_dir = base_path.config_dir(chain_spec.id());
let (keystore, public) = match self.keystore_params.keystore_config(&config_dir)? {
KeystoreConfig::Path { path, password } => {
let public = with_crypto_scheme!(self.scheme, to_vec(&suri, password.clone()))?;
let keystore: KeystorePtr = LocalKeystore::open(path, password)?.into();
(keystore, public)
},
_ => unreachable!("keystore_config always returns path and password; qed"),
};
let key_type =
KeyTypeId::try_from(self.key_type.as_str()).map_err(|_| Error::KeyTypeInvalid)?;
keystore
.insert(key_type, &suri, &public[..])
.map_err(|_| Error::KeystoreOperation)?;
Ok(())
}
}
fn to_vec<P: sp_core::Pair>(uri: &str, pass: Option<SecretString>) -> Result<Vec<u8>, Error> {
let p = utils::pair_from_suri::<P>(uri, pass)?;
Ok(p.public().as_ref().to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
use sc_service::{ChainSpec, ChainType, GenericChainSpec, NoExtension};
use sp_core::{sr25519::Pair, ByteArray, Pair as _};
use sp_keystore::Keystore;
use tempfile::TempDir;
struct Cli;
impl SubstrateCli for Cli {
fn impl_name() -> String {
"test".into()
}
fn impl_version() -> String {
"2.0".into()
}
fn description() -> String {
"test".into()
}
fn support_url() -> String {
"test.test".into()
}
fn copyright_start_year() -> i32 {
2021
}
fn author() -> String {
"test".into()
}
fn load_spec(&self, _: &str) -> std::result::Result<Box<dyn ChainSpec>, String> {
let builder =
GenericChainSpec::<NoExtension, ()>::builder(Default::default(), NoExtension::None);
Ok(Box::new(
builder
.with_name("test")
.with_id("test_id")
.with_chain_type(ChainType::Development)
.with_genesis_config_patch(Default::default())
.build(),
))
}
}
#[test]
fn insert_with_custom_base_path() {
let path = TempDir::new().unwrap();
let path_str = format!("{}", path.path().display());
let (key, uri, _) = Pair::generate_with_phrase(None);
let inspect = InsertKeyCmd::parse_from(&[
"insert-key",
"-d",
&path_str,
"--key-type",
"test",
"--suri",
&uri,
"--scheme=sr25519",
]);
assert!(inspect.run(&Cli).is_ok());
let keystore =
LocalKeystore::open(path.path().join("chains").join("test_id").join("keystore"), None)
.unwrap();
assert!(keystore.has_keys(&[(key.public().to_raw_vec(), KeyTypeId(*b"test"))]));
}
}
@@ -0,0 +1,264 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the `inspect` subcommand
use crate::{
utils::{self, print_from_public, print_from_uri},
with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams, NetworkSchemeFlag, OutputTypeFlag,
};
use clap::Parser;
use sp_core::crypto::{ExposeSecret, SecretString, SecretUri, Ss58Codec};
use std::str::FromStr;
/// The `inspect` command
#[derive(Debug, Parser)]
#[command(
name = "inspect",
about = "Gets a public key and a SS58 address from the provided Secret URI"
)]
pub struct InspectKeyCmd {
/// A Key URI to be inspected. May be a secret seed, secret URI
/// (with derivation paths and password), SS58, public URI or a hex encoded public key.
/// If it is a hex encoded public key, `--public` needs to be given as argument.
/// If the given value is a file, the file content will be used
/// as URI.
/// If omitted, you will be prompted for the URI.
uri: Option<String>,
/// Is the given `uri` a hex encoded public key?
#[arg(long)]
public: bool,
#[allow(missing_docs)]
#[clap(flatten)]
pub keystore_params: KeystoreParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub network_scheme: NetworkSchemeFlag,
#[allow(missing_docs)]
#[clap(flatten)]
pub output_scheme: OutputTypeFlag,
#[allow(missing_docs)]
#[clap(flatten)]
pub crypto_scheme: CryptoSchemeFlag,
/// Expect that `--uri` has the given public key/account-id.
/// If `--uri` has any derivations, the public key is checked against the base `uri`, i.e. the
/// `uri` without any derivation applied. However, if `uri` has a password or there is one
/// given by `--password`, it will be used to decrypt `uri` before comparing the public
/// key/account-id.
/// If there is no derivation in `--uri`, the public key will be checked against the public key
/// of `--uri` directly.
#[arg(long, conflicts_with = "public")]
pub expect_public: Option<String>,
}
impl InspectKeyCmd {
/// Run the command
pub fn run(&self) -> Result<(), Error> {
let uri = utils::read_uri(self.uri.as_ref())?;
let password = self.keystore_params.read_password()?;
if self.public {
with_crypto_scheme!(
self.crypto_scheme.scheme,
print_from_public(
&uri,
self.network_scheme.network,
self.output_scheme.output_type,
)
)?;
} else {
if let Some(ref expect_public) = self.expect_public {
with_crypto_scheme!(
self.crypto_scheme.scheme,
expect_public_from_phrase(expect_public, &uri, password.as_ref())
)?;
}
with_crypto_scheme!(
self.crypto_scheme.scheme,
print_from_uri(
&uri,
password,
self.network_scheme.network,
self.output_scheme.output_type,
)
);
}
Ok(())
}
}
/// Checks that `expect_public` is the public key of `suri`.
///
/// If `suri` has any derivations, `expect_public` is checked against the public key of the "bare"
/// `suri`, i.e. without any derivations.
///
/// Returns an error if the public key does not match.
fn expect_public_from_phrase<Pair: sp_core::Pair>(
expect_public: &str,
suri: &str,
password: Option<&SecretString>,
) -> Result<(), Error> {
let secret_uri = SecretUri::from_str(suri).map_err(|e| format!("{:?}", e))?;
let expected_public = if let Some(public) = expect_public.strip_prefix("0x") {
let hex_public = array_bytes::hex2bytes(public)
.map_err(|_| format!("Invalid expected public key hex: `{}`", expect_public))?;
Pair::Public::try_from(&hex_public)
.map_err(|_| format!("Invalid expected public key: `{}`", expect_public))?
} else {
Pair::Public::from_string_with_version(expect_public)
.map_err(|_| format!("Invalid expected account id: `{}`", expect_public))?
.0
};
let pair = Pair::from_string_with_seed(
secret_uri.phrase.expose_secret().as_str(),
password
.or_else(|| secret_uri.password.as_ref())
.map(|p| p.expose_secret().as_str()),
)
.map_err(|_| format!("Invalid secret uri: {}", suri))?
.0;
if pair.public() == expected_public {
Ok(())
} else {
Err(format!("Expected public ({}) key does not match.", expect_public).into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_core::crypto::{ByteArray, Pair};
use sp_runtime::traits::IdentifyAccount;
#[test]
fn inspect() {
let words =
"remember fiber forum demise paper uniform squirrel feel access exclude casual effort";
let seed = "0xad1fb77243b536b90cfe5f0d351ab1b1ac40e3890b41dc64f766ee56340cfca5";
let inspect = InspectKeyCmd::parse_from(&["inspect-key", words, "--password", "12345"]);
assert!(inspect.run().is_ok());
let inspect = InspectKeyCmd::parse_from(&["inspect-key", seed]);
assert!(inspect.run().is_ok());
}
#[test]
fn inspect_public_key() {
let public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069";
let inspect = InspectKeyCmd::parse_from(&["inspect-key", "--public", public]);
assert!(inspect.run().is_ok());
}
#[test]
fn inspect_with_expected_public_key() {
let check_cmd = |seed, expected_public, success| {
let inspect = InspectKeyCmd::parse_from(&[
"inspect-key",
"--expect-public",
expected_public,
seed,
]);
let res = inspect.run();
if success {
assert!(res.is_ok());
} else {
assert!(res.unwrap_err().to_string().contains(&format!(
"Expected public ({}) key does not match.",
expected_public
)));
}
};
let seed =
"remember fiber forum demise paper uniform squirrel feel access exclude casual effort";
let invalid_public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069";
let valid_public = sp_core::sr25519::Pair::from_string_with_seed(seed, None)
.expect("Valid")
.0
.public();
let valid_public_hex = array_bytes::bytes2hex("0x", valid_public.as_slice());
let valid_accountid = format!("{}", valid_public.into_account());
// It should fail with the invalid public key
check_cmd(seed, invalid_public, false);
// It should work with the valid public key & account id
check_cmd(seed, &valid_public_hex, true);
check_cmd(seed, &valid_accountid, true);
let password = "test12245";
let seed_with_password = format!("{}///{}", seed, password);
let valid_public_with_password =
sp_core::sr25519::Pair::from_string_with_seed(&seed_with_password, Some(password))
.expect("Valid")
.0
.public();
let valid_public_hex_with_password =
array_bytes::bytes2hex("0x", valid_public_with_password.as_slice());
let valid_accountid_with_password =
format!("{}", &valid_public_with_password.into_account());
// Only the public key that corresponds to the seed with password should be accepted.
check_cmd(&seed_with_password, &valid_public_hex, false);
check_cmd(&seed_with_password, &valid_accountid, false);
check_cmd(&seed_with_password, &valid_public_hex_with_password, true);
check_cmd(&seed_with_password, &valid_accountid_with_password, true);
let seed_with_password_and_derivation = format!("{}//test//account///{}", seed, password);
let valid_public_with_password_and_derivation =
sp_core::sr25519::Pair::from_string_with_seed(
&seed_with_password_and_derivation,
Some(password),
)
.expect("Valid")
.0
.public();
let valid_public_hex_with_password_and_derivation =
array_bytes::bytes2hex("0x", valid_public_with_password_and_derivation.as_slice());
// They should still be valid, because we check the base secret key.
check_cmd(&seed_with_password_and_derivation, &valid_public_hex_with_password, true);
check_cmd(&seed_with_password_and_derivation, &valid_accountid_with_password, true);
// And these should be invalid.
check_cmd(&seed_with_password_and_derivation, &valid_public_hex, false);
check_cmd(&seed_with_password_and_derivation, &valid_accountid, false);
// The public of the derived account should fail.
check_cmd(
&seed_with_password_and_derivation,
&valid_public_hex_with_password_and_derivation,
false,
);
}
}
@@ -0,0 +1,98 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the `inspect-node-key` subcommand
use crate::Error;
use clap::Parser;
use libp2p_identity::Keypair;
use std::{
fs,
io::{self, Read},
path::PathBuf,
};
/// The `inspect-node-key` command
#[derive(Debug, Parser)]
#[command(
name = "inspect-node-key",
about = "Load a node key from a file or stdin and print the corresponding peer-id."
)]
pub struct InspectNodeKeyCmd {
/// Name of file to read the secret key from.
/// If not given, the secret key is read from stdin (up to EOF).
#[arg(long)]
file: Option<PathBuf>,
/// The input is in raw binary format.
/// If not given, the input is read as an hex encoded string.
#[arg(long)]
bin: bool,
/// This argument is deprecated and has no effect for this command.
#[deprecated(note = "Network identifier is not used for node-key inspection")]
#[arg(short = 'n', long = "network", value_name = "NETWORK", ignore_case = true)]
pub network_scheme: Option<String>,
}
impl InspectNodeKeyCmd {
/// runs the command
pub fn run(&self) -> Result<(), Error> {
let mut file_data = match &self.file {
Some(file) => fs::read(&file)?,
None => {
let mut buf = Vec::with_capacity(64);
io::stdin().lock().read_to_end(&mut buf)?;
buf
},
};
if !self.bin {
// With hex input, give to the user a bit of tolerance about whitespaces
let keyhex = String::from_utf8_lossy(&file_data);
file_data = array_bytes::hex2bytes(keyhex.trim())
.map_err(|_| "failed to decode secret as hex")?;
}
let keypair =
Keypair::ed25519_from_bytes(&mut file_data).map_err(|_| "Bad node key file")?;
println!("{}", keypair.public().to_peer_id());
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::commands::generate_node_key::GenerateNodeKeyCmd;
use super::*;
#[test]
fn inspect_node_key() {
let path = tempfile::tempdir().unwrap().into_path().join("node-id").into_os_string();
let path = path.to_str().unwrap();
let cmd = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", path]);
assert!(cmd.run("test", &String::from("test")).is_ok());
let cmd = InspectNodeKeyCmd::parse_from(&["inspect-node-key", "--file", path]);
assert!(cmd.run().is_ok());
}
}
+61
View File
@@ -0,0 +1,61 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Key related CLI utilities
use super::{
generate::GenerateCmd, generate_node_key::GenerateNodeKeyCmd, insert_key::InsertKeyCmd,
inspect_key::InspectKeyCmd, inspect_node_key::InspectNodeKeyCmd,
};
use crate::{Error, SubstrateCli};
/// Key utilities for the cli.
#[derive(Debug, clap::Subcommand)]
pub enum KeySubcommand {
/// Generate a random node key, write it to a file or stdout and write the
/// corresponding peer-id to stderr
GenerateNodeKey(GenerateNodeKeyCmd),
/// Generate a random account
Generate(GenerateCmd),
/// Gets a public key and a SS58 address from the provided Secret URI
Inspect(InspectKeyCmd),
/// Load a node key from a file or stdin and print the corresponding peer-id
InspectNodeKey(InspectNodeKeyCmd),
/// Insert a key to the keystore of a node.
Insert(InsertKeyCmd),
}
impl KeySubcommand {
/// run the key subcommands
pub fn run<C: SubstrateCli>(&self, cli: &C) -> Result<(), Error> {
match self {
KeySubcommand::GenerateNodeKey(cmd) => {
let chain_spec = cli.load_spec(cmd.chain.as_deref().unwrap_or(""))?;
cmd.run(chain_spec.id(), &C::executable_name())
},
KeySubcommand::Generate(cmd) => cmd.run(),
KeySubcommand::Inspect(cmd) => cmd.run(),
KeySubcommand::Insert(cmd) => cmd.run(cli),
KeySubcommand::InspectNodeKey(cmd) => cmd.run(),
}
}
}
+51
View File
@@ -0,0 +1,51 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Various subcommands that can be included in a substrate-based chain's CLI.
mod build_spec_cmd;
mod chain_info_cmd;
mod check_block_cmd;
mod export_blocks_cmd;
mod export_chain_spec_cmd;
mod export_state_cmd;
mod generate;
mod generate_node_key;
mod import_blocks_cmd;
mod insert_key;
mod inspect_key;
mod inspect_node_key;
mod key;
mod purge_chain_cmd;
mod revert_cmd;
mod run_cmd;
mod sign;
mod test;
pub mod utils;
mod vanity;
mod verify;
pub use self::{
build_spec_cmd::BuildSpecCmd, chain_info_cmd::ChainInfoCmd, check_block_cmd::CheckBlockCmd,
export_blocks_cmd::ExportBlocksCmd, export_chain_spec_cmd::ExportChainSpecCmd,
export_state_cmd::ExportStateCmd, generate::GenerateCmd,
generate_node_key::GenerateKeyCmdCommon, import_blocks_cmd::ImportBlocksCmd,
insert_key::InsertKeyCmd, inspect_key::InspectKeyCmd, inspect_node_key::InspectNodeKeyCmd,
key::KeySubcommand, purge_chain_cmd::PurgeChainCmd, revert_cmd::RevertCmd, run_cmd::RunCmd,
sign::SignCmd, vanity::VanityCmd, verify::VerifyCmd,
};
@@ -0,0 +1,94 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{DatabaseParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use sc_service::DatabaseSource;
use std::{
fmt::Debug,
fs,
io::{self, Write},
};
/// The `purge-chain` command used to remove the whole chain.
#[derive(Debug, Clone, Parser)]
pub struct PurgeChainCmd {
/// Skip interactive prompt by answering yes automatically.
#[arg(short = 'y')]
pub yes: bool,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub database_params: DatabaseParams,
}
impl PurgeChainCmd {
/// Run the purge command
pub fn run(&self, database_config: DatabaseSource) -> error::Result<()> {
let db_path = database_config.path().and_then(|p| p.parent()).ok_or_else(|| {
error::Error::Input("Cannot purge custom database implementation".into())
})?;
if !self.yes {
print!("Are you sure to remove {:?}? [y/N]: ", &db_path);
io::stdout().flush().expect("failed to flush stdout");
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim();
match input.chars().next() {
Some('y') | Some('Y') => {},
_ => {
println!("Aborted");
return Ok(());
},
}
}
match fs::remove_dir_all(&db_path) {
Ok(_) => {
println!("{:?} removed.", &db_path);
Ok(())
},
Err(ref err) if err.kind() == io::ErrorKind::NotFound => {
eprintln!("{:?} did not exist.", &db_path);
Ok(())
},
Err(err) => Result::Err(err.into()),
}
}
}
impl CliConfiguration for PurgeChainCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn database_params(&self) -> Option<&DatabaseParams> {
Some(&self.database_params)
}
}
@@ -0,0 +1,90 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{DatabaseParams, GenericNumber, PruningParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use sc_client_api::{Backend, UsageProvider};
use sc_service::chain_ops::revert_chain;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
use std::{fmt::Debug, str::FromStr, sync::Arc};
/// The `revert` command used revert the chain to a previous state.
#[derive(Debug, Parser)]
pub struct RevertCmd {
/// Number of blocks to revert.
#[arg(default_value = "256")]
pub num: GenericNumber,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub pruning_params: PruningParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub database_params: DatabaseParams,
}
/// Revert handler for auxiliary data (e.g. consensus).
type AuxRevertHandler<C, BA, B> =
Box<dyn FnOnce(Arc<C>, Arc<BA>, NumberFor<B>) -> error::Result<()>>;
impl RevertCmd {
/// Run the revert command
pub async fn run<B, BA, C>(
&self,
client: Arc<C>,
backend: Arc<BA>,
aux_revert: Option<AuxRevertHandler<C, BA, B>>,
) -> error::Result<()>
where
B: BlockT,
BA: Backend<B>,
C: UsageProvider<B>,
<<<B as BlockT>::Header as HeaderT>::Number as FromStr>::Err: Debug,
{
let blocks = self.num.parse()?;
if let Some(aux_revert) = aux_revert {
aux_revert(client.clone(), backend.clone(), blocks)?;
}
revert_chain(client, backend, blocks)?;
Ok(())
}
}
impl CliConfiguration for RevertCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn pruning_params(&self) -> Option<&PruningParams> {
Some(&self.pruning_params)
}
fn database_params(&self) -> Option<&DatabaseParams> {
Some(&self.database_params)
}
}
@@ -0,0 +1,421 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error::{Error, Result},
params::{
ImportParams, KeystoreParams, NetworkParams, OffchainWorkerParams, RpcEndpoint,
SharedParams, TransactionPoolParams,
},
CliConfiguration, PrometheusParams, RpcParams, RuntimeParams, TelemetryParams,
};
use clap::Parser;
use regex::Regex;
use sc_service::{
config::{
BasePath, IpNetwork, PrometheusConfig, RpcBatchRequestConfig, TransactionPoolOptions,
},
ChainSpec, Role,
};
use sc_telemetry::TelemetryEndpoints;
use std::num::NonZeroU32;
/// The `run` command used to run a node.
#[derive(Debug, Clone, Parser)]
pub struct RunCmd {
/// Enable validator mode.
///
/// The node will be started with the authority role and actively
/// participate in any consensus task that it can (e.g. depending on
/// availability of local keys).
#[arg(long)]
pub validator: bool,
/// Disable GRANDPA.
///
/// Disables voter when running in validator mode, otherwise disable the GRANDPA
/// observer.
#[arg(long)]
pub no_grandpa: bool,
/// The human-readable name for this node.
///
/// It's used as network node name.
#[arg(long, value_name = "NAME")]
pub name: Option<String>,
#[allow(missing_docs)]
#[clap(flatten)]
pub rpc_params: RpcParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub telemetry_params: TelemetryParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub prometheus_params: PrometheusParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub runtime_params: RuntimeParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub offchain_worker_params: OffchainWorkerParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub import_params: ImportParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub network_params: NetworkParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub pool_config: TransactionPoolParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub keystore_params: KeystoreParams,
/// Shortcut for `--name Alice --validator`.
///
/// Session keys for `Alice` are added to keystore.
#[arg(long, conflicts_with_all = &["bob", "charlie", "dave", "eve", "ferdie", "one", "two"])]
pub alice: bool,
/// Shortcut for `--name Bob --validator`.
///
/// Session keys for `Bob` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "charlie", "dave", "eve", "ferdie", "one", "two"])]
pub bob: bool,
/// Shortcut for `--name Charlie --validator`.
///
/// Session keys for `Charlie` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "bob", "dave", "eve", "ferdie", "one", "two"])]
pub charlie: bool,
/// Shortcut for `--name Dave --validator`.
///
/// Session keys for `Dave` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "eve", "ferdie", "one", "two"])]
pub dave: bool,
/// Shortcut for `--name Eve --validator`.
///
/// Session keys for `Eve` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "ferdie", "one", "two"])]
pub eve: bool,
/// Shortcut for `--name Ferdie --validator`.
///
/// Session keys for `Ferdie` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "one", "two"])]
pub ferdie: bool,
/// Shortcut for `--name One --validator`.
///
/// Session keys for `One` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "two"])]
pub one: bool,
/// Shortcut for `--name Two --validator`.
///
/// Session keys for `Two` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "one"])]
pub two: bool,
/// Enable authoring even when offline.
#[arg(long)]
pub force_authoring: bool,
/// Run a temporary node.
///
/// A temporary directory will be created to store the configuration and will be deleted
/// at the end of the process.
///
/// Note: the directory is random per process execution. This directory is used as base path
/// which includes: database, node key and keystore.
///
/// When `--dev` is given and no explicit `--base-path`, this option is implied.
#[arg(long, conflicts_with = "base_path")]
pub tmp: bool,
}
impl RunCmd {
/// Get the `Sr25519Keyring` matching one of the flag.
pub fn get_keyring(&self) -> Option<sp_keyring::Sr25519Keyring> {
use sp_keyring::Sr25519Keyring::*;
if self.alice {
Some(Alice)
} else if self.bob {
Some(Bob)
} else if self.charlie {
Some(Charlie)
} else if self.dave {
Some(Dave)
} else if self.eve {
Some(Eve)
} else if self.ferdie {
Some(Ferdie)
} else if self.one {
Some(One)
} else if self.two {
Some(Two)
} else {
None
}
}
}
impl CliConfiguration for RunCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn import_params(&self) -> Option<&ImportParams> {
Some(&self.import_params)
}
fn network_params(&self) -> Option<&NetworkParams> {
Some(&self.network_params)
}
fn keystore_params(&self) -> Option<&KeystoreParams> {
Some(&self.keystore_params)
}
fn offchain_worker_params(&self) -> Option<&OffchainWorkerParams> {
Some(&self.offchain_worker_params)
}
fn node_name(&self) -> Result<String> {
let name: String = match (self.name.as_ref(), self.get_keyring()) {
(Some(name), _) => name.to_string(),
(_, Some(keyring)) => keyring.to_string(),
(None, None) => crate::generate_node_name(),
};
is_node_name_valid(&name).map_err(|msg| {
Error::Input(format!(
"Invalid node name '{}'. Reason: {}. If unsure, use none.",
name, msg
))
})?;
Ok(name)
}
fn dev_key_seed(&self, is_dev: bool) -> Result<Option<String>> {
Ok(self.get_keyring().map(|a| format!("//{}", a)).or_else(|| {
if is_dev {
Some("//Alice".into())
} else {
None
}
}))
}
fn telemetry_endpoints(
&self,
chain_spec: &Box<dyn ChainSpec>,
) -> Result<Option<TelemetryEndpoints>> {
let params = &self.telemetry_params;
Ok(if params.no_telemetry {
None
} else if !params.telemetry_endpoints.is_empty() {
Some(
TelemetryEndpoints::new(params.telemetry_endpoints.clone())
.map_err(|e| e.to_string())?,
)
} else {
chain_spec.telemetry_endpoints().clone()
})
}
fn role(&self, is_dev: bool) -> Result<Role> {
let keyring = self.get_keyring();
let is_authority = self.validator || is_dev || keyring.is_some();
Ok(if is_authority { Role::Authority } else { Role::Full })
}
fn force_authoring(&self) -> Result<bool> {
// Imply forced authoring on --dev
Ok(self.shared_params.dev || self.force_authoring)
}
fn prometheus_config(
&self,
default_listen_port: u16,
chain_spec: &Box<dyn ChainSpec>,
) -> Result<Option<PrometheusConfig>> {
Ok(self
.prometheus_params
.prometheus_config(default_listen_port, chain_spec.id().to_string()))
}
fn disable_grandpa(&self) -> Result<bool> {
Ok(self.no_grandpa)
}
fn rpc_max_connections(&self) -> Result<u32> {
Ok(self.rpc_params.rpc_max_connections)
}
fn rpc_cors(&self, is_dev: bool) -> Result<Option<Vec<String>>> {
self.rpc_params.rpc_cors(is_dev)
}
fn rpc_addr(&self, default_listen_port: u16) -> Result<Option<Vec<RpcEndpoint>>> {
self.rpc_params.rpc_addr(self.is_dev()?, self.validator, default_listen_port)
}
fn rpc_methods(&self) -> Result<sc_service::config::RpcMethods> {
Ok(self.rpc_params.rpc_methods.into())
}
fn rpc_max_request_size(&self) -> Result<u32> {
Ok(self.rpc_params.rpc_max_request_size)
}
fn rpc_max_response_size(&self) -> Result<u32> {
Ok(self.rpc_params.rpc_max_response_size)
}
fn rpc_max_subscriptions_per_connection(&self) -> Result<u32> {
Ok(self.rpc_params.rpc_max_subscriptions_per_connection)
}
fn rpc_buffer_capacity_per_connection(&self) -> Result<u32> {
Ok(self.rpc_params.rpc_message_buffer_capacity_per_connection)
}
fn rpc_batch_config(&self) -> Result<RpcBatchRequestConfig> {
self.rpc_params.rpc_batch_config()
}
fn rpc_rate_limit(&self) -> Result<Option<NonZeroU32>> {
Ok(self.rpc_params.rpc_rate_limit)
}
fn rpc_rate_limit_whitelisted_ips(&self) -> Result<Vec<IpNetwork>> {
Ok(self.rpc_params.rpc_rate_limit_whitelisted_ips.clone())
}
fn rpc_rate_limit_trust_proxy_headers(&self) -> Result<bool> {
Ok(self.rpc_params.rpc_rate_limit_trust_proxy_headers)
}
fn transaction_pool(&self, is_dev: bool) -> Result<TransactionPoolOptions> {
Ok(self.pool_config.transaction_pool(is_dev))
}
fn max_runtime_instances(&self) -> Result<Option<usize>> {
Ok(Some(self.runtime_params.max_runtime_instances))
}
fn runtime_cache_size(&self) -> Result<u8> {
Ok(self.runtime_params.runtime_cache_size)
}
fn base_path(&self) -> Result<Option<BasePath>> {
Ok(if self.tmp {
Some(BasePath::new_temp_dir()?)
} else {
match self.shared_params().base_path()? {
Some(r) => Some(r),
// If `dev` is enabled, we use the temp base path.
None if self.shared_params().is_dev() => Some(BasePath::new_temp_dir()?),
None => None,
}
})
}
}
/// Check whether a node name is considered as valid.
pub fn is_node_name_valid(_name: &str) -> std::result::Result<(), &str> {
let name = _name.to_string();
if name.is_empty() {
return Err("Node name cannot be empty");
}
if name.chars().count() >= crate::NODE_NAME_MAX_LENGTH {
return Err("Node name too long");
}
let invalid_chars = r"[\\.@]";
let re = Regex::new(invalid_chars).unwrap();
if re.is_match(&name) {
return Err("Node name should not contain invalid chars such as '.' and '@'");
}
let invalid_patterns = r"^https?:";
let re = Regex::new(invalid_patterns).unwrap();
if re.is_match(&name) {
return Err("Node name should not contain urls");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tests_node_name_good() {
assert!(is_node_name_valid("short name").is_ok());
assert!(is_node_name_valid("www").is_ok());
assert!(is_node_name_valid("aawww").is_ok());
assert!(is_node_name_valid("wwwaa").is_ok());
assert!(is_node_name_valid("www aa").is_ok());
}
#[test]
fn tests_node_name_bad() {
assert!(is_node_name_valid("").is_err());
assert!(is_node_name_valid(
"very very long names are really not very cool for the ui at all, really they're not"
)
.is_err());
assert!(is_node_name_valid("Dots.not.Ok").is_err());
// NOTE: the urls below don't include a domain otherwise
// they'd get filtered for including a `.`
assert!(is_node_name_valid("http://visitme").is_err());
assert!(is_node_name_valid("http:/visitme").is_err());
assert!(is_node_name_valid("http:visitme").is_err());
assert!(is_node_name_valid("https://visitme").is_err());
assert!(is_node_name_valid("https:/visitme").is_err());
assert!(is_node_name_valid("https:visitme").is_err());
assert!(is_node_name_valid("www.visit.me").is_err());
assert!(is_node_name_valid("www.visit").is_err());
assert!(is_node_name_valid("hello\\world").is_err());
assert!(is_node_name_valid("visit.www").is_err());
assert!(is_node_name_valid("email@domain").is_err());
}
}
+126
View File
@@ -0,0 +1,126 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the `sign` subcommand
use crate::{
error, params::MessageParams, utils, with_crypto_scheme, CryptoSchemeFlag, KeystoreParams,
};
use array_bytes::bytes2hex;
use clap::Parser;
use sp_core::crypto::SecretString;
use std::io::{BufRead, Write};
/// The `sign` command
#[derive(Debug, Clone, Parser)]
#[command(name = "sign", about = "Sign a message, with a given (secret) key")]
pub struct SignCmd {
/// The secret key URI.
/// If the value is a file, the file content is used as URI.
/// If not given, you will be prompted for the URI.
#[arg(long)]
suri: Option<String>,
#[allow(missing_docs)]
#[clap(flatten)]
pub message_params: MessageParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub keystore_params: KeystoreParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub crypto_scheme: CryptoSchemeFlag,
}
impl SignCmd {
/// Run the command
pub fn run(&self) -> error::Result<()> {
let sig = self.sign(|| std::io::stdin().lock())?;
std::io::stdout().lock().write_all(sig.as_bytes())?;
Ok(())
}
/// Sign a message.
///
/// The message can either be provided as immediate argument via CLI or otherwise read from the
/// reader created by `create_reader`. The reader will only be created in case that the message
/// is not passed as immediate.
pub(crate) fn sign<F, R>(&self, create_reader: F) -> error::Result<String>
where
R: BufRead,
F: FnOnce() -> R,
{
let message = self.message_params.message_from(create_reader)?;
let suri = utils::read_uri(self.suri.as_ref())?;
let password = self.keystore_params.read_password()?;
with_crypto_scheme!(self.crypto_scheme.scheme, sign(&suri, password, message))
}
}
fn sign<P: sp_core::Pair>(
suri: &str,
password: Option<SecretString>,
message: Vec<u8>,
) -> error::Result<String> {
let pair = utils::pair_from_suri::<P>(suri, password)?;
Ok(bytes2hex("0x", pair.sign(&message).as_ref()))
}
#[cfg(test)]
mod test {
use super::*;
const SEED: &str = "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a";
#[test]
fn sign_arg() {
let cmd = SignCmd::parse_from(&[
"sign",
"--suri",
&SEED,
"--message",
&SEED,
"--password",
"12345",
"--hex",
]);
let sig = cmd.sign(|| std::io::stdin().lock()).expect("Must sign");
assert!(sig.starts_with("0x"), "Signature must start with 0x");
assert!(array_bytes::hex2bytes(&sig).is_ok(), "Signature is valid hex");
}
#[test]
fn sign_stdin() {
let cmd = SignCmd::parse_from(&[
"sign",
"--suri",
SEED,
"--message",
&SEED,
"--password",
"12345",
]);
let sig = cmd.sign(|| SEED.as_bytes()).expect("Must sign");
assert!(sig.starts_with("0x"), "Signature must start with 0x");
assert!(array_bytes::hex2bytes(&sig).is_ok(), "Signature is valid hex");
}
}
@@ -0,0 +1,21 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Integration tests for subkey commands.
mod sig_verify;
@@ -0,0 +1,152 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#![cfg(test)]
//! Integration test that the `sign` and `verify` sub-commands work together.
use crate::*;
use clap::Parser;
const SEED: &str = "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a";
const ALICE: &str = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
const BOB: &str = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty";
/// Sign a valid UFT-8 message which can be `hex` and passed either via `stdin` or as an argument.
fn sign(msg: &str, hex: bool, stdin: bool) -> String {
sign_raw(msg.as_bytes(), hex, stdin)
}
/// Sign a raw message which can be `hex` and passed either via `stdin` or as an argument.
fn sign_raw(msg: &[u8], hex: bool, stdin: bool) -> String {
let mut args = vec!["sign", "--suri", SEED];
if !stdin {
args.push("--message");
args.push(std::str::from_utf8(msg).expect("Can only pass valid UTF-8 as arg"));
}
if hex {
args.push("--hex");
}
let cmd = SignCmd::parse_from(&args);
cmd.sign(|| msg).expect("Static data is good; Must sign; qed")
}
/// Verify a valid UFT-8 message which can be `hex` and passed either via `stdin` or as an argument.
fn verify(msg: &str, hex: bool, stdin: bool, who: &str, sig: &str) -> bool {
verify_raw(msg.as_bytes(), hex, stdin, who, sig)
}
/// Verify a raw message which can be `hex` and passed either via `stdin` or as an argument.
fn verify_raw(msg: &[u8], hex: bool, stdin: bool, who: &str, sig: &str) -> bool {
let mut args = vec!["verify", sig, who];
if !stdin {
args.push("--message");
args.push(std::str::from_utf8(msg).expect("Can only pass valid UTF-8 as arg"));
}
if hex {
args.push("--hex");
}
let cmd = VerifyCmd::parse_from(&args);
cmd.verify(|| msg).is_ok()
}
/// Test that sig/verify works with UTF-8 bytes passed as arg.
#[test]
fn sig_verify_arg_utf8_work() {
let sig = sign("Something", false, false);
assert!(verify("Something", false, false, ALICE, &sig));
assert!(!verify("Something", false, false, BOB, &sig));
assert!(!verify("Wrong", false, false, ALICE, &sig));
assert!(!verify("Not hex", true, false, ALICE, &sig));
assert!(!verify("0x1234", true, false, ALICE, &sig));
assert!(!verify("Wrong", false, false, BOB, &sig));
assert!(!verify("Not hex", true, false, BOB, &sig));
assert!(!verify("0x1234", true, false, BOB, &sig));
}
/// Test that sig/verify works with UTF-8 bytes passed via stdin.
#[test]
fn sig_verify_stdin_utf8_work() {
let sig = sign("Something", false, true);
assert!(verify("Something", false, true, ALICE, &sig));
assert!(!verify("Something", false, true, BOB, &sig));
assert!(!verify("Wrong", false, true, ALICE, &sig));
assert!(!verify("Not hex", true, true, ALICE, &sig));
assert!(!verify("0x1234", true, true, ALICE, &sig));
assert!(!verify("Wrong", false, true, BOB, &sig));
assert!(!verify("Not hex", true, true, BOB, &sig));
assert!(!verify("0x1234", true, true, BOB, &sig));
}
/// Test that sig/verify works with hex bytes passed as arg.
#[test]
fn sig_verify_arg_hex_work() {
let sig = sign("0xaabbcc", true, false);
assert!(verify("0xaabbcc", true, false, ALICE, &sig));
assert!(verify("aabBcc", true, false, ALICE, &sig));
assert!(verify("0xaAbbCC", true, false, ALICE, &sig));
assert!(!verify("0xaabbcc", true, false, BOB, &sig));
assert!(!verify("0xaabbcc", false, false, ALICE, &sig));
}
/// Test that sig/verify works with hex bytes passed via stdin.
#[test]
fn sig_verify_stdin_hex_work() {
let sig = sign("0xaabbcc", true, true);
assert!(verify("0xaabbcc", true, true, ALICE, &sig));
assert!(verify("aabBcc", true, true, ALICE, &sig));
assert!(verify("0xaAbbCC", true, true, ALICE, &sig));
assert!(!verify("0xaabbcc", true, true, BOB, &sig));
assert!(!verify("0xaabbcc", false, true, ALICE, &sig));
}
/// Test that sig/verify works with random bytes.
#[test]
fn sig_verify_stdin_non_utf8_work() {
use rand::RngCore;
let mut rng = rand::thread_rng();
for _ in 0..100 {
let mut raw = [0u8; 32];
rng.fill_bytes(&mut raw);
let sig = sign_raw(&raw, false, true);
assert!(verify_raw(&raw, false, true, ALICE, &sig));
assert!(!verify_raw(&raw, false, true, BOB, &sig));
}
}
/// Test that sig/verify works with invalid UTF-8 bytes.
#[test]
fn sig_verify_stdin_invalid_utf8_work() {
let raw = vec![192u8, 193];
assert!(String::from_utf8(raw.clone()).is_err(), "Must be invalid UTF-8");
let sig = sign_raw(&raw, false, true);
assert!(verify_raw(&raw, false, true, ALICE, &sig));
assert!(!verify_raw(&raw, false, true, BOB, &sig));
}
+301
View File
@@ -0,0 +1,301 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! subcommand utilities
use crate::{
error::{self, Error},
OutputType,
};
use serde_json::json;
use sp_core::{
crypto::{
unwrap_or_default_ss58_version, ExposeSecret, SecretString, Ss58AddressFormat, Ss58Codec,
Zeroize,
},
hexdisplay::HexDisplay,
Pair,
};
use sp_runtime::{traits::IdentifyAccount, MultiSigner};
use std::path::PathBuf;
/// Public key type for Runtime
pub type PublicFor<P> = <P as sp_core::Pair>::Public;
/// Seed type for Runtime
pub type SeedFor<P> = <P as sp_core::Pair>::Seed;
/// helper method to fetch uri from `Option<String>` either as a file or read from stdin
pub fn read_uri(uri: Option<&String>) -> error::Result<String> {
let uri = if let Some(uri) = uri {
let file = PathBuf::from(&uri);
if file.is_file() {
std::fs::read_to_string(uri)?.trim_end().to_owned()
} else {
uri.into()
}
} else {
rpassword::prompt_password("URI: ")?
};
Ok(uri)
}
/// Try to parse given `uri` and print relevant information.
///
/// 1. Try to construct the `Pair` while using `uri` as input for [`sp_core::Pair::from_phrase`].
///
/// 2. Try to construct the `Pair` while using `uri` as input for
/// [`sp_core::Pair::from_string_with_seed`].
///
/// 3. Try to construct the `Pair::Public` while using `uri` as input for
/// [`sp_core::crypto::Ss58Codec::from_string_with_version`].
pub fn print_from_uri<Pair>(
uri: &str,
password: Option<SecretString>,
network_override: Option<Ss58AddressFormat>,
output: OutputType,
) where
Pair: sp_core::Pair,
Pair::Public: Into<MultiSigner>,
{
let password = password.as_ref().map(|s| s.expose_secret().as_str());
let network_id = String::from(unwrap_or_default_ss58_version(network_override));
if let Ok((pair, seed)) = Pair::from_phrase(uri, password) {
let public_key = pair.public();
let network_override = unwrap_or_default_ss58_version(network_override);
match output {
OutputType::Json => {
let json = json!({
"secretPhrase": uri,
"networkId": network_id,
"secretSeed": format_seed::<Pair>(seed),
"publicKey": format_public_key::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"accountId": format_account_id::<Pair>(public_key),
"ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override),
});
println!(
"{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed")
);
},
OutputType::Text => {
println!(
"Secret phrase: {}\n \
Network ID: {}\n \
Secret seed: {}\n \
Public key (hex): {}\n \
Account ID: {}\n \
Public key (SS58): {}\n \
SS58 Address: {}",
uri,
network_id,
format_seed::<Pair>(seed),
format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override),
pair.public().into().into_account().to_ss58check_with_version(network_override),
);
},
}
} else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password) {
let public_key = pair.public();
let network_override = unwrap_or_default_ss58_version(network_override);
match output {
OutputType::Json => {
let json = json!({
"secretKeyUri": uri,
"networkId": network_id,
"secretSeed": if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() },
"publicKey": format_public_key::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"accountId": format_account_id::<Pair>(public_key),
"ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override),
});
println!(
"{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed")
);
},
OutputType::Text => {
println!(
"Secret Key URI `{}` is account:\n \
Network ID: {}\n \
Secret seed: {}\n \
Public key (hex): {}\n \
Account ID: {}\n \
Public key (SS58): {}\n \
SS58 Address: {}",
uri,
network_id,
if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() },
format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override),
pair.public().into().into_account().to_ss58check_with_version(network_override),
);
},
}
} else if let Ok((public_key, network)) = Pair::Public::from_string_with_version(uri) {
let network_override = network_override.unwrap_or(network);
match output {
OutputType::Json => {
let json = json!({
"publicKeyUri": uri,
"networkId": String::from(network_override),
"publicKey": format_public_key::<Pair>(public_key.clone()),
"accountId": format_account_id::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"ss58Address": public_key.to_ss58check_with_version(network_override),
});
println!(
"{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed")
);
},
OutputType::Text => {
println!(
"Public Key URI `{}` is account:\n \
Network ID/Version: {}\n \
Public key (hex): {}\n \
Account ID: {}\n \
Public key (SS58): {}\n \
SS58 Address: {}",
uri,
String::from(network_override),
format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override),
public_key.to_ss58check_with_version(network_override),
);
},
}
} else {
println!("Invalid phrase/URI given");
}
}
/// Try to parse given `public` as hex encoded public key and print relevant information.
pub fn print_from_public<Pair>(
public_str: &str,
network_override: Option<Ss58AddressFormat>,
output: OutputType,
) -> Result<(), Error>
where
Pair: sp_core::Pair,
Pair::Public: Into<MultiSigner>,
{
let public = array_bytes::hex2bytes(public_str)?;
let public_key = Pair::Public::try_from(&public)
.map_err(|_| "Failed to construct public key from given hex")?;
let network_override = unwrap_or_default_ss58_version(network_override);
match output {
OutputType::Json => {
let json = json!({
"networkId": String::from(network_override),
"publicKey": format_public_key::<Pair>(public_key.clone()),
"accountId": format_account_id::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"ss58Address": public_key.to_ss58check_with_version(network_override),
});
println!("{}", serde_json::to_string_pretty(&json).expect("Json pretty print failed"));
},
OutputType::Text => {
println!(
"Network ID/Version: {}\n \
Public key (hex): {}\n \
Account ID: {}\n \
Public key (SS58): {}\n \
SS58 Address: {}",
String::from(network_override),
format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override),
public_key.to_ss58check_with_version(network_override),
);
},
}
Ok(())
}
/// generate a pair from suri
pub fn pair_from_suri<P: Pair>(suri: &str, password: Option<SecretString>) -> Result<P, Error> {
let result = if let Some(pass) = password {
let mut pass_str = pass.expose_secret().clone();
let pair = P::from_string(suri, Some(&pass_str));
pass_str.zeroize();
pair
} else {
P::from_string(suri, None)
};
Ok(result.map_err(|err| format!("Invalid phrase {:?}", err))?)
}
/// formats seed as hex
pub fn format_seed<P: sp_core::Pair>(seed: SeedFor<P>) -> String {
format!("0x{}", HexDisplay::from(&seed.as_ref()))
}
/// formats public key as hex
fn format_public_key<P: sp_core::Pair>(public_key: PublicFor<P>) -> String {
format!("0x{}", HexDisplay::from(&public_key.as_ref()))
}
/// formats public key as accountId as hex
fn format_account_id<P: sp_core::Pair>(public_key: PublicFor<P>) -> String
where
PublicFor<P>: Into<MultiSigner>,
{
format!("0x{}", HexDisplay::from(&public_key.into().into_account().as_ref()))
}
/// Allows for calling $method with appropriate crypto impl.
#[macro_export]
macro_rules! with_crypto_scheme {
(
$scheme:expr,
$method:ident ( $($params:expr),* $(,)?) $(,)?
) => {
$crate::with_crypto_scheme!($scheme, $method<>($($params),*))
};
(
$scheme:expr,
$method:ident<$($generics:ty),*>( $( $params:expr ),* $(,)?) $(,)?
) => {
match $scheme {
$crate::CryptoScheme::Ecdsa => {
$method::<sp_core::ecdsa::Pair, $($generics),*>($($params),*)
}
$crate::CryptoScheme::Sr25519 => {
$method::<sp_core::sr25519::Pair, $($generics),*>($($params),*)
}
$crate::CryptoScheme::Ed25519 => {
$method::<sp_core::ed25519::Pair, $($generics),*>($($params),*)
}
}
};
}
+226
View File
@@ -0,0 +1,226 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! implementation of the `vanity` subcommand
use crate::{
error, utils, with_crypto_scheme, CryptoSchemeFlag, NetworkSchemeFlag, OutputTypeFlag,
};
use clap::Parser;
use rand::{rngs::OsRng, RngCore};
use sp_core::crypto::{unwrap_or_default_ss58_version, Ss58AddressFormat, Ss58Codec};
use sp_runtime::traits::IdentifyAccount;
use utils::print_from_uri;
/// The `vanity` command
#[derive(Debug, Clone, Parser)]
#[command(name = "vanity", about = "Generate a seed that provides a vanity address")]
pub struct VanityCmd {
/// Desired pattern
#[arg(long, value_parser = assert_non_empty_string)]
pattern: String,
#[allow(missing_docs)]
#[clap(flatten)]
network_scheme: NetworkSchemeFlag,
#[allow(missing_docs)]
#[clap(flatten)]
output_scheme: OutputTypeFlag,
#[allow(missing_docs)]
#[clap(flatten)]
crypto_scheme: CryptoSchemeFlag,
}
impl VanityCmd {
/// Run the command
pub fn run(&self) -> error::Result<()> {
let formatted_seed = with_crypto_scheme!(
self.crypto_scheme.scheme,
generate_key(
&self.pattern,
unwrap_or_default_ss58_version(self.network_scheme.network)
),
)?;
with_crypto_scheme!(
self.crypto_scheme.scheme,
print_from_uri(
&formatted_seed,
None,
self.network_scheme.network,
self.output_scheme.output_type,
),
);
Ok(())
}
}
/// genertae a key based on given pattern
fn generate_key<Pair>(
desired: &str,
network_override: Ss58AddressFormat,
) -> Result<String, &'static str>
where
Pair: sp_core::Pair,
Pair::Public: IdentifyAccount,
<Pair::Public as IdentifyAccount>::AccountId: Ss58Codec,
{
println!("Generating key containing pattern '{}'", desired);
let top = 45 + (desired.len() * 48);
let mut best = 0;
let mut seed = Pair::Seed::default();
let mut done = 0;
loop {
if done % 100000 == 0 {
OsRng.fill_bytes(seed.as_mut());
} else {
next_seed(seed.as_mut());
}
let p = Pair::from_seed(&seed);
let ss58 = p.public().into_account().to_ss58check_with_version(network_override);
let score = calculate_score(desired, &ss58);
if score > best || desired.len() < 2 {
best = score;
if best >= top {
println!("best: {} == top: {}", best, top);
return Ok(utils::format_seed::<Pair>(seed.clone()));
}
}
done += 1;
if done % good_waypoint(done) == 0 {
println!("{} keys searched; best is {}/{} complete", done, best, top);
}
}
}
fn good_waypoint(done: u64) -> u64 {
match done {
0..=1_000_000 => 100_000,
1_000_001..=10_000_000 => 1_000_000,
10_000_001..=100_000_000 => 10_000_000,
100_000_001.. => 100_000_000,
}
}
fn next_seed(seed: &mut [u8]) {
for s in seed {
match s {
255 => {
*s = 0;
},
_ => {
*s += 1;
break;
},
}
}
}
/// Calculate the score of a key based on the desired
/// input.
fn calculate_score(_desired: &str, key: &str) -> usize {
for truncate in 0.._desired.len() {
let snip_size = _desired.len() - truncate;
let truncated = &_desired[0..snip_size];
if let Some(pos) = key.find(truncated) {
return (47 - pos) + (snip_size * 48);
}
}
0
}
/// checks that `pattern` is non-empty
fn assert_non_empty_string(pattern: &str) -> Result<String, &'static str> {
if pattern.is_empty() {
Err("Pattern must not be empty")
} else {
Ok(pattern.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_core::{
crypto::{default_ss58_version, Ss58AddressFormatRegistry, Ss58Codec},
sr25519, Pair,
};
#[test]
fn vanity() {
let vanity = VanityCmd::parse_from(&["vanity", "--pattern", "j"]);
assert!(vanity.run().is_ok());
}
#[test]
fn test_generation_with_single_char() {
let seed = generate_key::<sr25519::Pair>("ab", default_ss58_version()).unwrap();
assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
.unwrap()
.public()
.to_ss58check()
.contains("ab"));
}
#[test]
fn generate_key_respects_network_override() {
let seed =
generate_key::<sr25519::Pair>("ab", Ss58AddressFormatRegistry::PezkuwiAccount.into())
.unwrap();
assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
.unwrap()
.public()
.to_ss58check_with_version(Ss58AddressFormatRegistry::PezkuwiAccount.into())
.contains("ab"));
}
#[test]
fn test_score_1_char_100() {
let score = calculate_score("j", "5jolkadotwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim");
assert_eq!(score, 94);
}
#[test]
fn test_score_100() {
let score = calculate_score("Pezkuwi", "5PezkuwiwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim");
assert_eq!(score, 430);
}
#[test]
fn test_score_50_2() {
// 50% for the position + 50% for the size
assert_eq!(
calculate_score("Pezkuwi", "5PolkXXXXwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim"),
238
);
}
#[test]
fn test_score_0() {
assert_eq!(
calculate_score("Pezkuwi", "5GUWv4bLCchGUHJrzULXnh4JgXsMpTKRnjuXTY7Qo1Kh9uYK"),
0
);
}
}
+137
View File
@@ -0,0 +1,137 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! implementation of the `verify` subcommand
use crate::{error, params::MessageParams, utils, with_crypto_scheme, CryptoSchemeFlag};
use clap::Parser;
use sp_core::crypto::{ByteArray, Ss58Codec};
use std::io::BufRead;
/// The `verify` command
#[derive(Debug, Clone, Parser)]
#[command(
name = "verify",
about = "Verify a signature for a message, provided on STDIN, with a given (public or secret) key"
)]
pub struct VerifyCmd {
/// Signature, hex-encoded.
sig: String,
/// The public or secret key URI.
/// If the value is a file, the file content is used as URI.
/// If not given, you will be prompted for the URI.
uri: Option<String>,
#[allow(missing_docs)]
#[clap(flatten)]
pub message_params: MessageParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub crypto_scheme: CryptoSchemeFlag,
}
impl VerifyCmd {
/// Run the command
pub fn run(&self) -> error::Result<()> {
self.verify(|| std::io::stdin().lock())
}
/// Verify a signature for a message.
///
/// The message can either be provided as immediate argument via CLI or otherwise read from the
/// reader created by `create_reader`. The reader will only be created in case that the message
/// is not passed as immediate.
pub(crate) fn verify<F, R>(&self, create_reader: F) -> error::Result<()>
where
R: BufRead,
F: FnOnce() -> R,
{
let message = self.message_params.message_from(create_reader)?;
let sig_data = array_bytes::hex2bytes(&self.sig)?;
let uri = utils::read_uri(self.uri.as_ref())?;
let uri = if let Some(uri) = uri.strip_prefix("0x") { uri } else { &uri };
with_crypto_scheme!(self.crypto_scheme.scheme, verify(sig_data, message, uri))
}
}
fn verify<Pair>(sig_data: Vec<u8>, message: Vec<u8>, uri: &str) -> error::Result<()>
where
Pair: sp_core::Pair,
Pair::Signature: for<'a> TryFrom<&'a [u8]>,
{
let signature =
Pair::Signature::try_from(&sig_data).map_err(|_| error::Error::SignatureFormatInvalid)?;
let pubkey = if let Ok(pubkey_vec) = array_bytes::hex2bytes(uri) {
Pair::Public::from_slice(pubkey_vec.as_slice())
.map_err(|_| error::Error::KeyFormatInvalid)?
} else {
Pair::Public::from_string(uri)?
};
if Pair::verify(&signature, &message, &pubkey) {
println!("Signature verifies correctly.");
} else {
return Err(error::Error::SignatureInvalid);
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
const ALICE: &str = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
const SIG1: &str = "0x4eb25a2285a82374888880af0024eb30c3a21ce086eae3862888d345af607f0ad6fb081312f11730932564f24a9f8ebcee2d46861413ae61307eca58db2c3e81";
const SIG2: &str = "0x026342225155056ea797118c1c8c8b3cc002aa2020c36f4217fa3c302783a572ad3dcd38c231cbaf86cadb93984d329c963ceac0685cc1ee4c1ed50fa443a68f";
// Verify work with `--message` argument.
#[test]
fn verify_immediate() {
let cmd = VerifyCmd::parse_from(&["verify", SIG1, ALICE, "--message", "test message"]);
assert!(cmd.run().is_ok(), "Alice' signature should verify");
}
// Verify work without `--message` argument.
#[test]
fn verify_stdin() {
let cmd = VerifyCmd::parse_from(&["verify", SIG1, ALICE]);
let message = "test message";
assert!(cmd.verify(|| message.as_bytes()).is_ok(), "Alice' signature should verify");
}
// Verify work with `--message` argument for hex message.
#[test]
fn verify_immediate_hex() {
let cmd = VerifyCmd::parse_from(&["verify", SIG2, ALICE, "--message", "0xaabbcc", "--hex"]);
assert!(cmd.run().is_ok(), "Alice' signature should verify");
}
// Verify work without `--message` argument for hex message.
#[test]
fn verify_stdin_hex() {
let cmd = VerifyCmd::parse_from(&["verify", SIG2, ALICE, "--hex"]);
assert!(cmd.verify(|| "0xaabbcc".as_bytes()).is_ok());
assert!(cmd.verify(|| "aabbcc".as_bytes()).is_ok());
assert!(cmd.verify(|| "0xaABBcC".as_bytes()).is_ok());
}
}
+730
View File
@@ -0,0 +1,730 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Configuration trait for a CLI based on substrate
use crate::{
arg_enums::Database, error::Result, DatabaseParams, ImportParams, KeystoreParams,
NetworkParams, NodeKeyParams, OffchainWorkerParams, PruningParams, RpcEndpoint, SharedParams,
SubstrateCli,
};
use log::warn;
use names::{Generator, Name};
use sc_service::{
config::{
BasePath, Configuration, DatabaseSource, ExecutorConfiguration, IpNetwork, KeystoreConfig,
NetworkConfiguration, NodeKeyConfig, OffchainWorkerConfig, PrometheusConfig, PruningMode,
Role, RpcBatchRequestConfig, RpcConfiguration, RpcMethods, TelemetryEndpoints,
TransactionPoolOptions, WasmExecutionMethod,
},
BlocksPruning, ChainSpec, TracingReceiver,
};
use sc_tracing::logging::LoggerBuilder;
use std::{num::NonZeroU32, path::PathBuf};
/// The maximum number of characters for a node name.
pub(crate) const NODE_NAME_MAX_LENGTH: usize = 64;
/// Default sub directory to store network config.
pub(crate) const DEFAULT_NETWORK_CONFIG_PATH: &str = "network";
/// The recommended open file descriptor limit to be configured for the process.
const RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT: u64 = 10_000;
/// The default port.
pub const RPC_DEFAULT_PORT: u16 = 9944;
/// The default max number of subscriptions per connection.
pub const RPC_DEFAULT_MAX_SUBS_PER_CONN: u32 = 1024;
/// The default max request size in MB.
pub const RPC_DEFAULT_MAX_REQUEST_SIZE_MB: u32 = 15;
/// The default max response size in MB.
pub const RPC_DEFAULT_MAX_RESPONSE_SIZE_MB: u32 = 15;
/// The default concurrent connection limit.
pub const RPC_DEFAULT_MAX_CONNECTIONS: u32 = 100;
/// The default number of messages the RPC server
/// is allowed to keep in memory per connection.
pub const RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN: u32 = 64;
/// Default configuration values used by Substrate
///
/// These values will be used by [`CliConfiguration`] to set
/// default values for e.g. the listen port or the RPC port.
pub trait DefaultConfigurationValues {
/// The port Substrate should listen on for p2p connections.
///
/// By default this is `30333`.
fn p2p_listen_port() -> u16 {
30333
}
/// The port Substrate should listen on for JSON-RPC connections.
///
/// By default this is `9944`.
fn rpc_listen_port() -> u16 {
RPC_DEFAULT_PORT
}
/// The port Substrate should listen on for prometheus connections.
///
/// By default this is `9615`.
fn prometheus_listen_port() -> u16 {
9615
}
}
impl DefaultConfigurationValues for () {}
/// A trait that allows converting an object to a Configuration
pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
/// Get the SharedParams for this object
fn shared_params(&self) -> &SharedParams;
/// Get the ImportParams for this object
fn import_params(&self) -> Option<&ImportParams> {
None
}
/// Get the PruningParams for this object
fn pruning_params(&self) -> Option<&PruningParams> {
self.import_params().map(|x| &x.pruning_params)
}
/// Get the KeystoreParams for this object
fn keystore_params(&self) -> Option<&KeystoreParams> {
None
}
/// Get the NetworkParams for this object
fn network_params(&self) -> Option<&NetworkParams> {
None
}
/// Get a reference to `OffchainWorkerParams` for this object.
fn offchain_worker_params(&self) -> Option<&OffchainWorkerParams> {
None
}
/// Get the NodeKeyParams for this object
fn node_key_params(&self) -> Option<&NodeKeyParams> {
self.network_params().map(|x| &x.node_key_params)
}
/// Get the DatabaseParams for this object
fn database_params(&self) -> Option<&DatabaseParams> {
self.import_params().map(|x| &x.database_params)
}
/// Get the base path of the configuration (if any)
///
/// By default this is retrieved from `SharedParams`.
fn base_path(&self) -> Result<Option<BasePath>> {
self.shared_params().base_path()
}
/// Returns `true` if the node is for development or not
///
/// By default this is retrieved from `SharedParams`.
fn is_dev(&self) -> Result<bool> {
Ok(self.shared_params().is_dev())
}
/// Gets the role
///
/// By default this is `Role::Full`.
fn role(&self, _is_dev: bool) -> Result<Role> {
Ok(Role::Full)
}
/// Get the transaction pool options
///
/// By default this is `TransactionPoolOptions::default()`.
fn transaction_pool(&self, _is_dev: bool) -> Result<TransactionPoolOptions> {
Ok(Default::default())
}
/// Get the network configuration
///
/// By default this is retrieved from `NetworkParams` if it is available otherwise it creates
/// a default `NetworkConfiguration` based on `node_name`, `client_id`, `node_key` and
/// `net_config_dir`.
fn network_config(
&self,
chain_spec: &Box<dyn ChainSpec>,
is_dev: bool,
is_validator: bool,
net_config_dir: PathBuf,
client_id: &str,
node_name: &str,
node_key: NodeKeyConfig,
default_listen_port: u16,
) -> Result<NetworkConfiguration> {
let network_config = if let Some(network_params) = self.network_params() {
network_params.network_config(
chain_spec,
is_dev,
is_validator,
Some(net_config_dir),
client_id,
node_name,
node_key,
default_listen_port,
)
} else {
NetworkConfiguration::new(node_name, client_id, node_key, Some(net_config_dir))
};
// TODO: Return error here in the next release:
// https://github.com/pezkuwichain/pezkuwi-sdk/issues/139
// if is_validator && network_config.public_addresses.is_empty() {}
Ok(network_config)
}
/// Get the keystore configuration.
///
/// By default this is retrieved from `KeystoreParams` if it is available. Otherwise it uses
/// `KeystoreConfig::InMemory`.
fn keystore_config(&self, config_dir: &PathBuf) -> Result<KeystoreConfig> {
self.keystore_params()
.map(|x| x.keystore_config(config_dir))
.unwrap_or_else(|| Ok(KeystoreConfig::InMemory))
}
/// Get the database cache size.
///
/// By default this is retrieved from `DatabaseParams` if it is available. Otherwise its `None`.
fn database_cache_size(&self) -> Result<Option<usize>> {
Ok(self.database_params().map(|x| x.database_cache_size()).unwrap_or_default())
}
/// Get the database backend variant.
///
/// By default this is retrieved from `DatabaseParams` if it is available. Otherwise its `None`.
fn database(&self) -> Result<Option<Database>> {
Ok(self.database_params().and_then(|x| x.database()))
}
/// Get the database configuration object for the parameters provided
fn database_config(
&self,
base_path: &PathBuf,
cache_size: usize,
database: Database,
) -> Result<DatabaseSource> {
let role_dir = "full";
let rocksdb_path = base_path.join("db").join(role_dir);
let paritydb_path = base_path.join("paritydb").join(role_dir);
Ok(match database {
#[cfg(feature = "rocksdb")]
Database::RocksDb => DatabaseSource::RocksDb { path: rocksdb_path, cache_size },
Database::ParityDb => DatabaseSource::ParityDb { path: paritydb_path },
Database::ParityDbDeprecated => {
eprintln!(
"WARNING: \"paritydb-experimental\" database setting is deprecated and will be removed in future releases. \
Please update your setup to use the new value: \"paritydb\"."
);
DatabaseSource::ParityDb { path: paritydb_path }
},
Database::Auto => DatabaseSource::Auto { paritydb_path, rocksdb_path, cache_size },
})
}
/// Get the trie cache maximum size.
///
/// By default this is retrieved from `ImportParams` if it is available. Otherwise its `0`.
/// If `None` is returned the trie cache is disabled.
fn trie_cache_maximum_size(&self) -> Result<Option<usize>> {
Ok(self.import_params().map(|x| x.trie_cache_maximum_size()).unwrap_or_default())
}
/// Get if we should warm up the trie cache.
///
/// By default this is retrieved from `ImportParams` if it is available. Otherwise its `None`.
fn warm_up_trie_cache(&self) -> Result<Option<sc_service::config::TrieCacheWarmUpStrategy>> {
Ok(self
.import_params()
.map(|x| x.warm_up_trie_cache().map(|x| x.into()))
.unwrap_or_default())
}
/// Get the state pruning mode.
///
/// By default this is retrieved from `PruningMode` if it is available. Otherwise its
/// `PruningMode::default()`.
fn state_pruning(&self) -> Result<Option<PruningMode>> {
self.pruning_params()
.map(|x| x.state_pruning())
.unwrap_or_else(|| Ok(Default::default()))
}
/// Get the block pruning mode.
///
/// By default this is retrieved from `block_pruning` if it is available. Otherwise its
/// `BlocksPruning::KeepFinalized`.
fn blocks_pruning(&self) -> Result<BlocksPruning> {
self.pruning_params()
.map(|x| x.blocks_pruning())
.unwrap_or_else(|| Ok(BlocksPruning::KeepFinalized))
}
/// Get the chain ID (string).
///
/// By default this is retrieved from `SharedParams`.
fn chain_id(&self, is_dev: bool) -> Result<String> {
Ok(self.shared_params().chain_id(is_dev))
}
/// Get the name of the node.
///
/// By default a random name is generated.
fn node_name(&self) -> Result<String> {
Ok(generate_node_name())
}
/// Get the WASM execution method.
///
/// By default this is retrieved from `ImportParams` if it is available. Otherwise its
/// `WasmExecutionMethod::default()`.
fn wasm_method(&self) -> Result<WasmExecutionMethod> {
Ok(self.import_params().map(|x| x.wasm_method()).unwrap_or_default())
}
/// Get the path where WASM overrides live.
///
/// By default this is `None`.
fn wasm_runtime_overrides(&self) -> Option<PathBuf> {
self.import_params().map(|x| x.wasm_runtime_overrides()).unwrap_or_default()
}
/// Get the RPC address.
fn rpc_addr(&self, _default_listen_port: u16) -> Result<Option<Vec<RpcEndpoint>>> {
Ok(None)
}
/// Returns the RPC method set to expose.
///
/// By default this is `RpcMethods::Auto` (unsafe RPCs are denied iff
/// `rpc_external` returns true, respectively).
fn rpc_methods(&self) -> Result<RpcMethods> {
Ok(Default::default())
}
/// Get the maximum number of RPC server connections.
fn rpc_max_connections(&self) -> Result<u32> {
Ok(RPC_DEFAULT_MAX_CONNECTIONS)
}
/// Get the RPC cors (`None` if disabled)
///
/// By default this is `Some(Vec::new())`.
fn rpc_cors(&self, _is_dev: bool) -> Result<Option<Vec<String>>> {
Ok(Some(Vec::new()))
}
/// Get maximum RPC request payload size.
fn rpc_max_request_size(&self) -> Result<u32> {
Ok(RPC_DEFAULT_MAX_REQUEST_SIZE_MB)
}
/// Get maximum RPC response payload size.
fn rpc_max_response_size(&self) -> Result<u32> {
Ok(RPC_DEFAULT_MAX_RESPONSE_SIZE_MB)
}
/// Get maximum number of subscriptions per connection.
fn rpc_max_subscriptions_per_connection(&self) -> Result<u32> {
Ok(RPC_DEFAULT_MAX_SUBS_PER_CONN)
}
/// The number of messages the RPC server is allowed to keep in memory per connection.
fn rpc_buffer_capacity_per_connection(&self) -> Result<u32> {
Ok(RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN)
}
/// RPC server batch request configuration.
fn rpc_batch_config(&self) -> Result<RpcBatchRequestConfig> {
Ok(RpcBatchRequestConfig::Unlimited)
}
/// RPC rate limit configuration.
fn rpc_rate_limit(&self) -> Result<Option<NonZeroU32>> {
Ok(None)
}
/// RPC rate limit whitelisted ip addresses.
fn rpc_rate_limit_whitelisted_ips(&self) -> Result<Vec<IpNetwork>> {
Ok(vec![])
}
/// RPC rate limit trust proxy headers.
fn rpc_rate_limit_trust_proxy_headers(&self) -> Result<bool> {
Ok(false)
}
/// Get the prometheus configuration (`None` if disabled)
///
/// By default this is `None`.
fn prometheus_config(
&self,
_default_listen_port: u16,
_chain_spec: &Box<dyn ChainSpec>,
) -> Result<Option<PrometheusConfig>> {
Ok(None)
}
/// Get the telemetry endpoints (if any)
///
/// By default this is retrieved from the chain spec loaded by `load_spec`.
fn telemetry_endpoints(
&self,
chain_spec: &Box<dyn ChainSpec>,
) -> Result<Option<TelemetryEndpoints>> {
Ok(chain_spec.telemetry_endpoints().clone())
}
/// Get the default value for heap pages
///
/// By default this is `None`.
fn default_heap_pages(&self) -> Result<Option<u64>> {
Ok(None)
}
/// Returns an offchain worker config wrapped in `Ok(_)`
///
/// By default offchain workers are disabled.
fn offchain_worker(&self, role: &Role) -> Result<OffchainWorkerConfig> {
self.offchain_worker_params()
.map(|x| x.offchain_worker(role))
.unwrap_or_else(|| Ok(OffchainWorkerConfig::default()))
}
/// Returns `Ok(true)` if authoring should be forced
///
/// By default this is `false`.
fn force_authoring(&self) -> Result<bool> {
Ok(Default::default())
}
/// Returns `Ok(true)` if grandpa should be disabled
///
/// By default this is `false`.
fn disable_grandpa(&self) -> Result<bool> {
Ok(Default::default())
}
/// Get the development key seed from the current object
///
/// By default this is `None`.
fn dev_key_seed(&self, _is_dev: bool) -> Result<Option<String>> {
Ok(Default::default())
}
/// Get the tracing targets from the current object (if any)
///
/// By default this is retrieved from [`SharedParams`] if it is available. Otherwise its
/// `None`.
fn tracing_targets(&self) -> Result<Option<String>> {
Ok(self.shared_params().tracing_targets())
}
/// Get the TracingReceiver value from the current object
///
/// By default this is retrieved from [`SharedParams`] if it is available. Otherwise its
/// `TracingReceiver::default()`.
fn tracing_receiver(&self) -> Result<TracingReceiver> {
Ok(self.shared_params().tracing_receiver())
}
/// Get the node key from the current object
///
/// By default this is retrieved from `NodeKeyParams` if it is available. Otherwise its
/// `NodeKeyConfig::default()`.
fn node_key(&self, net_config_dir: &PathBuf) -> Result<NodeKeyConfig> {
let is_dev = self.is_dev()?;
let role = self.role(is_dev)?;
self.node_key_params()
.map(|x| x.node_key(net_config_dir, role, is_dev))
.unwrap_or_else(|| Ok(Default::default()))
}
/// Get maximum runtime instances
///
/// By default this is `None`.
fn max_runtime_instances(&self) -> Result<Option<usize>> {
Ok(Default::default())
}
/// Get maximum different runtimes in cache
///
/// By default this is `2`.
fn runtime_cache_size(&self) -> Result<u8> {
Ok(2)
}
/// Activate or not the automatic announcing of blocks after import
///
/// By default this is `false`.
fn announce_block(&self) -> Result<bool> {
Ok(true)
}
/// Create a Configuration object from the current object
fn create_configuration<C: SubstrateCli>(
&self,
cli: &C,
tokio_handle: tokio::runtime::Handle,
) -> Result<Configuration> {
let is_dev = self.is_dev()?;
let chain_id = self.chain_id(is_dev)?;
let chain_spec = cli.load_spec(&chain_id)?;
let base_path = base_path_or_default(self.base_path()?, &C::executable_name());
let config_dir = build_config_dir(&base_path, chain_spec.id());
let net_config_dir = build_net_config_dir(&config_dir);
let client_id = C::client_id();
let database_cache_size = self.database_cache_size()?.unwrap_or(1024);
let database = self.database()?.unwrap_or(
#[cfg(feature = "rocksdb")]
{
Database::RocksDb
},
#[cfg(not(feature = "rocksdb"))]
{
Database::ParityDb
},
);
let node_key = self.node_key(&net_config_dir)?;
let role = self.role(is_dev)?;
let max_runtime_instances = self.max_runtime_instances()?.unwrap_or(8);
let is_validator = role.is_authority();
let keystore = self.keystore_config(&config_dir)?;
let telemetry_endpoints = self.telemetry_endpoints(&chain_spec)?;
let runtime_cache_size = self.runtime_cache_size()?;
let rpc_addrs: Option<Vec<sc_service::config::RpcEndpoint>> = self
.rpc_addr(DCV::rpc_listen_port())?
.map(|addrs| addrs.into_iter().map(Into::into).collect());
Ok(Configuration {
impl_name: C::impl_name(),
impl_version: C::impl_version(),
tokio_handle,
transaction_pool: self.transaction_pool(is_dev)?,
network: self.network_config(
&chain_spec,
is_dev,
is_validator,
net_config_dir,
client_id.as_str(),
self.node_name()?.as_str(),
node_key,
DCV::p2p_listen_port(),
)?,
keystore,
database: self.database_config(&config_dir, database_cache_size, database)?,
data_path: config_dir,
trie_cache_maximum_size: self.trie_cache_maximum_size()?,
warm_up_trie_cache: self.warm_up_trie_cache()?,
state_pruning: self.state_pruning()?,
blocks_pruning: self.blocks_pruning()?,
executor: ExecutorConfiguration {
wasm_method: self.wasm_method()?,
default_heap_pages: self.default_heap_pages()?,
max_runtime_instances,
runtime_cache_size,
},
wasm_runtime_overrides: self.wasm_runtime_overrides(),
rpc: RpcConfiguration {
addr: rpc_addrs,
methods: self.rpc_methods()?,
max_connections: self.rpc_max_connections()?,
cors: self.rpc_cors(is_dev)?,
max_request_size: self.rpc_max_request_size()?,
max_response_size: self.rpc_max_response_size()?,
id_provider: None,
max_subs_per_conn: self.rpc_max_subscriptions_per_connection()?,
port: DCV::rpc_listen_port(),
message_buffer_capacity: self.rpc_buffer_capacity_per_connection()?,
batch_config: self.rpc_batch_config()?,
rate_limit: self.rpc_rate_limit()?,
rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips()?,
rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers()?,
request_logger_limit: if is_dev { 1024 * 1024 } else { 1024 },
},
prometheus_config: self
.prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?,
telemetry_endpoints,
offchain_worker: self.offchain_worker(&role)?,
force_authoring: self.force_authoring()?,
disable_grandpa: self.disable_grandpa()?,
dev_key_seed: self.dev_key_seed(is_dev)?,
tracing_targets: self.tracing_targets()?,
tracing_receiver: self.tracing_receiver()?,
chain_spec,
announce_block: self.announce_block()?,
role,
base_path,
})
}
/// Get the filters for the logging.
///
/// This should be a list of comma-separated values.
/// Example: `foo=trace,bar=debug,baz=info`
///
/// By default this is retrieved from `SharedParams`.
fn log_filters(&self) -> Result<String> {
Ok(self.shared_params().log_filters().join(","))
}
/// Should the detailed log output be enabled.
fn detailed_log_output(&self) -> Result<bool> {
Ok(self.shared_params().detailed_log_output())
}
/// Is log reloading enabled?
fn enable_log_reloading(&self) -> Result<bool> {
Ok(self.shared_params().enable_log_reloading())
}
/// Should the log color output be disabled?
fn disable_log_color(&self) -> Result<bool> {
Ok(self.shared_params().disable_log_color())
}
/// Initialize substrate. This must be done only once per process.
///
/// This method:
///
/// 1. Sets the panic handler
/// 2. Optionally customize logger/profiling
/// 2. Initializes the logger
/// 3. Raises the FD limit
///
/// The `logger_hook` closure is executed before the logger is constructed
/// and initialized. It is useful for setting up a custom profiler.
///
/// Example:
/// ```
/// use sc_tracing::{SpanDatum, TraceEvent};
/// struct TestProfiler;
///
/// impl sc_tracing::TraceHandler for TestProfiler {
/// fn handle_span(&self, sd: &SpanDatum) {}
/// fn handle_event(&self, _event: &TraceEvent) {}
/// };
///
/// fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) -> () {
/// |logger_builder, config| {
/// logger_builder.with_custom_profiling(Box::new(TestProfiler{}));
/// }
/// }
/// ```
fn init<F>(&self, support_url: &String, impl_version: &String, logger_hook: F) -> Result<()>
where
F: FnOnce(&mut LoggerBuilder),
{
sp_panic_handler::set(support_url, impl_version);
let mut logger = LoggerBuilder::new(self.log_filters()?);
logger
.with_log_reloading(self.enable_log_reloading()?)
.with_detailed_output(self.detailed_log_output()?);
if let Some(tracing_targets) = self.tracing_targets()? {
let tracing_receiver = self.tracing_receiver()?;
logger.with_profiling(tracing_receiver, tracing_targets);
}
if self.disable_log_color()? {
logger.with_colors(false);
}
// Call hook for custom profiling setup.
logger_hook(&mut logger);
logger.init()?;
match fdlimit::raise_fd_limit() {
Ok(fdlimit::Outcome::LimitRaised { to, .. }) => {
if to < RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT {
warn!(
"Low open file descriptor limit configured for the process. \
Current value: {:?}, recommended value: {:?}.",
to, RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT,
);
}
},
Ok(fdlimit::Outcome::Unsupported) => {
// Unsupported platform (non-Linux)
},
Err(error) => {
warn!(
"Failed to configure file descriptor limit for the process: \
{}, recommended value: {:?}.",
error, RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT,
);
},
}
Ok(())
}
}
/// Generate a valid random name for the node
pub fn generate_node_name() -> String {
loop {
let node_name = Generator::with_naming(Name::Numbered)
.next()
.expect("RNG is available on all supported platforms; qed");
let count = node_name.chars().count();
if count < NODE_NAME_MAX_LENGTH {
return node_name;
}
}
}
/// Returns the value of `base_path` or the default_path if it is None
pub(crate) fn base_path_or_default(
base_path: Option<BasePath>,
executable_name: &String,
) -> BasePath {
base_path.unwrap_or_else(|| BasePath::from_project("", "", executable_name))
}
/// Returns the default path for configuration directory based on the chain_spec
pub(crate) fn build_config_dir(base_path: &BasePath, chain_spec_id: &str) -> PathBuf {
base_path.config_dir(chain_spec_id)
}
/// Returns the default path for the network configuration inside the configuration dir
pub(crate) fn build_net_config_dir(config_dir: &PathBuf) -> PathBuf {
config_dir.join(DEFAULT_NETWORK_CONFIG_PATH)
}
/// Returns the default path for the network directory starting from the provided base_path
/// or from the default base_path.
pub(crate) fn build_network_key_dir_or_default(
base_path: Option<BasePath>,
chain_spec_id: &str,
executable_name: &String,
) -> PathBuf {
let config_dir =
build_config_dir(&base_path_or_default(base_path, executable_name), chain_spec_id);
build_net_config_dir(&config_dir)
}
+121
View File
@@ -0,0 +1,121 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Initialization errors.
use std::path::PathBuf;
use sp_core::crypto;
/// Result type alias for the CLI.
pub type Result<T> = std::result::Result<T, Error>;
/// Error type for the CLI.
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Cli(#[from] clap::Error),
#[error(transparent)]
Service(#[from] sc_service::Error),
#[error(transparent)]
Client(#[from] sp_blockchain::Error),
#[error(transparent)]
Codec(#[from] codec::Error),
#[error("Invalid input: {0}")]
Input(String),
#[error("Invalid listen multiaddress")]
InvalidListenMultiaddress,
#[error("Invalid URI; expecting either a secret URI or a public URI.")]
InvalidUri(crypto::PublicError),
#[error("Signature is an invalid format.")]
SignatureFormatInvalid,
#[error("Key is an invalid format.")]
KeyFormatInvalid,
#[error("Unknown key type, must be a known 4-character sequence")]
KeyTypeInvalid,
#[error("Signature verification failed")]
SignatureInvalid,
#[error("Key store operation failed")]
KeystoreOperation,
#[error("Key storage issue encountered")]
KeyStorage(#[from] sc_keystore::Error),
#[error("Invalid hexadecimal string data, {0:?}")]
HexDataConversion(array_bytes::Error),
/// Application specific error chain sequence forwarder.
#[error(transparent)]
Application(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error(transparent)]
GlobalLoggerError(#[from] sc_tracing::logging::Error),
#[error(
"Starting an authorithy without network key in {0}.
\n This is not a safe operation because other authorities in the network may depend on your node having a stable identity.
\n Otherwise these other authorities may not being able to reach you.
\n If it is the first time running your node you could use one of the following methods:
\n 1. [Preferred] Separately generate the key with: <NODE_BINARY> key generate-node-key --base-path <YOUR_BASE_PATH>
\n 2. [Preferred] Separately generate the key with: <NODE_BINARY> key generate-node-key --file <YOUR_PATH_TO_NODE_KEY>
\n 3. [Preferred] Separately generate the key with: <NODE_BINARY> key generate-node-key --default-base-path
\n 4. [Unsafe] Pass --unsafe-force-node-key-generation and make sure you remove it for subsequent node restarts"
)]
NetworkKeyNotFound(PathBuf),
#[error("A network key already exists in path {0}")]
KeyAlreadyExistsInPath(PathBuf),
}
impl From<&str> for Error {
fn from(s: &str) -> Error {
Error::Input(s.to_string())
}
}
impl From<String> for Error {
fn from(s: String) -> Error {
Error::Input(s)
}
}
impl From<crypto::PublicError> for Error {
fn from(e: crypto::PublicError) -> Error {
Error::InvalidUri(e)
}
}
impl From<array_bytes::Error> for Error {
fn from(e: array_bytes::Error) -> Error {
Error::HexDataConversion(e)
}
}
+253
View File
@@ -0,0 +1,253 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Substrate CLI library.
//!
//! To see a full list of commands available, see [`commands`].
#![warn(missing_docs)]
#![warn(unused_extern_crates)]
#![warn(unused_imports)]
use clap::{CommandFactory, FromArgMatches, Parser};
use log::warn;
use sc_service::Configuration;
pub mod arg_enums;
pub mod commands;
mod config;
mod error;
mod params;
mod runner;
mod signals;
pub use arg_enums::*;
pub use clap;
pub use commands::*;
pub use config::*;
pub use error::*;
pub use params::*;
pub use runner::*;
pub use sc_service::{ChainSpec, Role};
pub use sc_tracing::logging::LoggerBuilder;
pub use signals::Signals;
pub use sp_version::RuntimeVersion;
/// Substrate client CLI
///
/// This trait needs to be implemented on the root CLI struct of the application. It will provide
/// the implementation `name`, `version`, `executable name`, `description`, `author`, `support_url`,
/// `copyright start year` and most importantly: how to load the chain spec.
pub trait SubstrateCli: Sized {
/// Implementation name.
fn impl_name() -> String;
/// Implementation version.
///
/// By default, it will look like this:
///
/// `2.0.0-b950f731c`
///
/// Where the hash is the short hash of the commit in the Git repository.
fn impl_version() -> String;
/// Executable file name.
///
/// Extracts the file name from `std::env::current_exe()`.
/// Resorts to the env var `CARGO_PKG_NAME` in case of Error.
fn executable_name() -> String {
std::env::current_exe()
.ok()
.and_then(|e| e.file_name().map(|s| s.to_os_string()))
.and_then(|w| w.into_string().ok())
.unwrap_or_else(|| env!("CARGO_PKG_NAME").into())
}
/// Executable file description.
fn description() -> String;
/// Executable file author.
fn author() -> String;
/// Support URL.
fn support_url() -> String;
/// Copyright starting year (x-current year)
fn copyright_start_year() -> i32;
/// Chain spec factory
fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn ChainSpec>, String>;
/// Helper function used to parse the command line arguments. This is the equivalent of
/// [`clap::Parser::parse()`].
///
/// To allow running the node without subcommand, it also sets a few more settings:
/// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`],
/// [`clap::Command::subcommand_negates_reqs`].
///
/// Creates `Self` from the command line arguments. Print the
/// error message and quit the program in case of failure.
fn from_args() -> Self
where
Self: Parser + Sized,
{
<Self as SubstrateCli>::from_iter(&mut std::env::args_os())
}
/// Helper function used to parse the command line arguments. This is the equivalent of
/// [`clap::Parser::parse_from`].
///
/// To allow running the node without subcommand, it also sets a few more settings:
/// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`],
/// [`clap::Command::subcommand_negates_reqs`].
///
/// Creates `Self` from any iterator over arguments.
/// Print the error message and quit the program in case of failure.
fn from_iter<I>(iter: I) -> Self
where
Self: Parser + Sized,
I: IntoIterator,
I::Item: Into<std::ffi::OsString> + Clone,
{
let app = <Self as CommandFactory>::command();
let app = Self::setup_command(app);
let matches = app.try_get_matches_from(iter).unwrap_or_else(|e| e.exit());
<Self as FromArgMatches>::from_arg_matches(&matches).unwrap_or_else(|e| e.exit())
}
/// Helper function used to parse the command line arguments. This is the equivalent of
/// [`clap::Parser::try_parse_from`]
///
/// To allow running the node without subcommand, it also sets a few more settings:
/// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`],
/// [`clap::Command::subcommand_negates_reqs`].
///
/// Creates `Self` from any iterator over arguments.
/// Print the error message and quit the program in case of failure.
///
/// **NOTE:** This method WILL NOT exit when `--help` or `--version` (or short versions) are
/// used. It will return a [`clap::Error`], where the [`clap::Error::kind`] is a
/// [`clap::error::ErrorKind::DisplayHelp`] or [`clap::error::ErrorKind::DisplayVersion`]
/// respectively. You must call [`clap::Error::exit`] or perform a [`std::process::exit`].
fn try_from_iter<I>(iter: I) -> clap::error::Result<Self>
where
Self: Parser + Sized,
I: IntoIterator,
I::Item: Into<std::ffi::OsString> + Clone,
{
let app = <Self as CommandFactory>::command();
let app = Self::setup_command(app);
let matches = app.try_get_matches_from(iter)?;
<Self as FromArgMatches>::from_arg_matches(&matches)
}
/// Returns the client ID: `{impl_name}/v{impl_version}`
fn client_id() -> String {
format!("{}/v{}", Self::impl_name(), Self::impl_version())
}
/// Only create a Configuration for the command provided in argument
fn create_configuration<T: CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
&self,
command: &T,
tokio_handle: tokio::runtime::Handle,
) -> error::Result<Configuration> {
command.create_configuration(self, tokio_handle)
}
/// Create a runner for the command provided in argument. This will create a Configuration and
/// a tokio runtime
fn create_runner<T: CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
&self,
command: &T,
) -> Result<Runner<Self>> {
self.create_runner_with_logger_hook(command, |_, _| {})
}
/// Create a runner for the command provided in argument. The `logger_hook` can be used to setup
/// a custom profiler or update the logger configuration before it is initialized.
///
/// Example:
/// ```
/// use sc_tracing::{SpanDatum, TraceEvent};
/// struct TestProfiler;
///
/// impl sc_tracing::TraceHandler for TestProfiler {
/// fn handle_span(&self, sd: &SpanDatum) {}
/// fn handle_event(&self, _event: &TraceEvent) {}
/// };
///
/// fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) -> () {
/// |logger_builder, config| {
/// logger_builder.with_custom_profiling(Box::new(TestProfiler{}));
/// }
/// }
/// ```
fn create_runner_with_logger_hook<
T: CliConfiguration<DVC>,
DVC: DefaultConfigurationValues,
F,
>(
&self,
command: &T,
logger_hook: F,
) -> Result<Runner<Self>>
where
F: FnOnce(&mut LoggerBuilder, &Configuration),
{
let tokio_runtime = build_runtime()?;
// `capture` needs to be called in a tokio context.
// Also capture them as early as possible.
let signals = tokio_runtime.block_on(async { Signals::capture() })?;
let config = command.create_configuration(self, tokio_runtime.handle().clone())?;
command.init(&Self::support_url(), &Self::impl_version(), |logger_builder| {
logger_hook(logger_builder, &config)
})?;
Runner::new(config, tokio_runtime, signals)
}
/// Augments a `clap::Command` with standard metadata like name, version, author, description,
/// etc.
///
/// This is used internally in `from_iter`, `try_from_iter` and can be used externally
/// to manually set up a command with Substrate CLI defaults.
fn setup_command(mut cmd: clap::Command) -> clap::Command {
let mut full_version = Self::impl_version();
full_version.push('\n');
cmd = cmd
.name(Self::executable_name())
.version(full_version)
.author(Self::author())
.about(Self::description())
.long_about(Self::description())
.after_help(format!("Support: {}", Self::support_url()))
.propagate_version(true)
.args_conflicts_with_subcommands(true)
.subcommand_negates_reqs(true);
cmd
}
}
@@ -0,0 +1,44 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::arg_enums::Database;
use clap::Args;
/// Parameters for database
#[derive(Debug, Clone, PartialEq, Args)]
pub struct DatabaseParams {
/// Select database backend to use.
#[arg(long, alias = "db", value_name = "DB", ignore_case = true, value_enum)]
pub database: Option<Database>,
/// Limit the memory the database cache can use.
#[arg(long = "db-cache", value_name = "MiB")]
pub database_cache_size: Option<usize>,
}
impl DatabaseParams {
/// Database backend
pub fn database(&self) -> Option<Database> {
self.database
}
/// Limit the memory the database cache can use.
pub fn database_cache_size(&self) -> Option<usize> {
self.database_cache_size
}
}
@@ -0,0 +1,204 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
arg_enums::{
ExecutionStrategy, WasmExecutionMethod, WasmtimeInstantiationStrategy,
DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD,
},
params::{DatabaseParams, PruningParams},
};
use clap::{Args, ValueEnum};
use std::path::PathBuf;
/// Parameters for block import.
#[derive(Debug, Clone, Args)]
pub struct ImportParams {
#[allow(missing_docs)]
#[clap(flatten)]
pub pruning_params: PruningParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub database_params: DatabaseParams,
/// Method for executing Wasm runtime code.
#[arg(
long = "wasm-execution",
value_name = "METHOD",
value_enum,
ignore_case = true,
default_value_t = DEFAULT_WASM_EXECUTION_METHOD,
)]
pub wasm_method: WasmExecutionMethod,
/// The WASM instantiation method to use.
///
/// Only has an effect when `wasm-execution` is set to `compiled`.
/// The copy-on-write strategies are only supported on Linux.
/// If the copy-on-write variant of a strategy is unsupported
/// the executor will fall back to the non-CoW equivalent.
/// The fastest (and the default) strategy available is `pooling-copy-on-write`.
/// The `legacy-instance-reuse` strategy is deprecated and will
/// be removed in the future. It should only be used in case of
/// issues with the default instantiation strategy.
#[arg(
long,
value_name = "STRATEGY",
default_value_t = DEFAULT_WASMTIME_INSTANTIATION_STRATEGY,
value_enum,
)]
pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy,
/// Specify the path where local WASM runtimes are stored.
///
/// These runtimes will override on-chain runtimes when the version matches.
#[arg(long, value_name = "PATH")]
pub wasm_runtime_overrides: Option<PathBuf>,
#[allow(missing_docs)]
#[clap(flatten)]
pub execution_strategies: ExecutionStrategiesParams,
/// Specify the state cache size.
///
/// Providing `0` will disable the cache.
#[arg(long, value_name = "Bytes", default_value_t = 1024 * 1024 * 1024)]
pub trie_cache_size: usize,
/// Warm up the trie cache.
///
/// No warmup if flag is not present. Using flag without value chooses non-blocking warmup.
#[arg(long, value_name = "STRATEGY", value_enum, num_args = 0..=1, default_missing_value = "non-blocking")]
pub warm_up_trie_cache: Option<TrieCacheWarmUpStrategy>,
}
/// Warmup strategy for the trie cache.
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum TrieCacheWarmUpStrategy {
/// Warm up the cache in a non-blocking way.
#[clap(name = "non-blocking")]
NonBlocking,
/// Warm up the cache in a blocking way (not recommended for production use).
///
/// When enabled, the trie cache warm-up will block the node startup until complete.
/// This is not recommended for production use as it can significantly delay node startup.
/// Only enable this option for testing or debugging purposes.
#[clap(name = "blocking")]
Blocking,
}
impl From<TrieCacheWarmUpStrategy> for sc_service::config::TrieCacheWarmUpStrategy {
fn from(strategy: TrieCacheWarmUpStrategy) -> Self {
match strategy {
TrieCacheWarmUpStrategy::NonBlocking =>
sc_service::config::TrieCacheWarmUpStrategy::NonBlocking,
TrieCacheWarmUpStrategy::Blocking =>
sc_service::config::TrieCacheWarmUpStrategy::Blocking,
}
}
}
impl ImportParams {
/// Specify the trie cache maximum size.
pub fn trie_cache_maximum_size(&self) -> Option<usize> {
if self.trie_cache_size == 0 {
None
} else {
Some(self.trie_cache_size)
}
}
/// Specify if we should warm up the trie cache.
pub fn warm_up_trie_cache(&self) -> Option<TrieCacheWarmUpStrategy> {
self.warm_up_trie_cache
}
/// Get the WASM execution method from the parameters
pub fn wasm_method(&self) -> sc_service::config::WasmExecutionMethod {
self.execution_strategies.check_usage_and_print_deprecation_warning();
crate::execution_method_from_cli(self.wasm_method, self.wasmtime_instantiation_strategy)
}
/// Enable overriding on-chain WASM with locally-stored WASM
/// by specifying the path where local WASM is stored.
pub fn wasm_runtime_overrides(&self) -> Option<PathBuf> {
self.wasm_runtime_overrides.clone()
}
}
/// Execution strategies parameters.
#[derive(Debug, Clone, Args)]
pub struct ExecutionStrategiesParams {
/// Runtime execution strategy for importing blocks during initial sync.
#[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)]
pub execution_syncing: Option<ExecutionStrategy>,
/// Runtime execution strategy for general block import (including locally authored blocks).
#[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)]
pub execution_import_block: Option<ExecutionStrategy>,
/// Runtime execution strategy for constructing blocks.
#[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)]
pub execution_block_construction: Option<ExecutionStrategy>,
/// Runtime execution strategy for offchain workers.
#[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)]
pub execution_offchain_worker: Option<ExecutionStrategy>,
/// Runtime execution strategy when not syncing, importing or constructing blocks.
#[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)]
pub execution_other: Option<ExecutionStrategy>,
/// The execution strategy that should be used by all execution contexts.
#[arg(
long,
value_name = "STRATEGY",
value_enum,
ignore_case = true,
conflicts_with_all = &[
"execution_other",
"execution_offchain_worker",
"execution_block_construction",
"execution_import_block",
"execution_syncing",
]
)]
pub execution: Option<ExecutionStrategy>,
}
impl ExecutionStrategiesParams {
/// Check if one of the parameters is still passed and print a warning if so.
fn check_usage_and_print_deprecation_warning(&self) {
for (param, name) in [
(&self.execution_syncing, "execution-syncing"),
(&self.execution_import_block, "execution-import-block"),
(&self.execution_block_construction, "execution-block-construction"),
(&self.execution_offchain_worker, "execution-offchain-worker"),
(&self.execution_other, "execution-other"),
(&self.execution, "execution"),
] {
if param.is_some() {
eprintln!(
"CLI parameter `--{name}` has no effect anymore and will be removed in the future!"
);
}
}
}
}
@@ -0,0 +1,103 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{error, error::Result};
use clap::Args;
use sc_service::config::KeystoreConfig;
use sp_core::crypto::SecretString;
use std::{
fs,
path::{Path, PathBuf},
};
/// default sub directory for the key store
const DEFAULT_KEYSTORE_CONFIG_PATH: &str = "keystore";
/// Parameters of the keystore
#[derive(Debug, Clone, Args)]
pub struct KeystoreParams {
/// Specify custom keystore path.
#[arg(long, value_name = "PATH")]
pub keystore_path: Option<PathBuf>,
/// Use interactive shell for entering the password used by the keystore.
#[arg(long, conflicts_with_all = &["password", "password_filename"])]
pub password_interactive: bool,
/// Password used by the keystore.
///
/// This allows appending an extra user-defined secret to the seed.
#[arg(
long,
value_parser = secret_string_from_str,
conflicts_with_all = &["password_interactive", "password_filename"]
)]
pub password: Option<SecretString>,
/// File that contains the password used by the keystore.
#[arg(
long,
value_name = "PATH",
conflicts_with_all = &["password_interactive", "password"]
)]
pub password_filename: Option<PathBuf>,
}
/// Parse a secret string, returning a displayable error.
pub fn secret_string_from_str(s: &str) -> std::result::Result<SecretString, String> {
std::str::FromStr::from_str(s).map_err(|_| "Could not get SecretString".to_string())
}
impl KeystoreParams {
/// Get the keystore configuration for the parameters
pub fn keystore_config(&self, config_dir: &Path) -> Result<KeystoreConfig> {
let password = if self.password_interactive {
Some(SecretString::new(input_keystore_password()?))
} else if let Some(ref file) = self.password_filename {
let password = fs::read_to_string(file).map_err(|e| format!("{}", e))?;
Some(SecretString::new(password))
} else {
self.password.clone()
};
let path = self
.keystore_path
.clone()
.unwrap_or_else(|| config_dir.join(DEFAULT_KEYSTORE_CONFIG_PATH));
Ok(KeystoreConfig::Path { path, password })
}
/// helper method to fetch password from `KeyParams` or read from stdin
pub fn read_password(&self) -> error::Result<Option<SecretString>> {
let (password_interactive, password) = (self.password_interactive, self.password.clone());
let pass = if password_interactive {
let password = rpassword::prompt_password("Key password: ")?;
Some(SecretString::new(password))
} else {
password
};
Ok(pass)
}
}
fn input_keystore_password() -> Result<String> {
rpassword::prompt_password("Keystore password: ").map_err(|e| format!("{:?}", e).into())
}
@@ -0,0 +1,120 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Params to configure how a message should be passed into a command.
use crate::error::Error;
use array_bytes::{hex2bytes, hex_bytes2hex_str};
use clap::Args;
use std::io::BufRead;
/// Params to configure how a message should be passed into a command.
#[derive(Debug, Clone, Args)]
pub struct MessageParams {
/// Message to process. Will be read from STDIN otherwise.
/// The message is assumed to be raw bytes per default. Use `--hex` for hex input. Can
/// optionally be prefixed with `0x` in the hex case.
#[arg(long)]
message: Option<String>,
/// The message is hex-encoded data.
#[arg(long)]
hex: bool,
}
impl MessageParams {
/// Produces the message by either using its immediate value or reading from stdin.
///
/// This function should only be called once and the result cached.
pub(crate) fn message_from<F, R>(&self, create_reader: F) -> Result<Vec<u8>, Error>
where
R: BufRead,
F: FnOnce() -> R,
{
let raw = match &self.message {
Some(raw) => raw.as_bytes().to_vec(),
None => {
let mut raw = vec![];
create_reader().read_to_end(&mut raw)?;
raw
},
};
if self.hex {
hex2bytes(hex_bytes2hex_str(&raw)?).map_err(Into::into)
} else {
Ok(raw)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Test that decoding an immediate message works.
#[test]
fn message_decode_immediate() {
for (name, input, hex, output) in test_closures() {
println!("Testing: immediate_{}", name);
let params = MessageParams { message: Some(input.into()), hex };
let message = params.message_from(|| std::io::stdin().lock());
match output {
Some(output) => {
let message = message.expect(&format!("{}: should decode but did not", name));
assert_eq!(message, output, "{}: decoded a wrong message", name);
},
None => {
message.err().expect(&format!("{}: should not decode but did", name));
},
}
}
}
/// Test that decoding a message from a stream works.
#[test]
fn message_decode_stream() {
for (name, input, hex, output) in test_closures() {
println!("Testing: stream_{}", name);
let params = MessageParams { message: None, hex };
let message = params.message_from(|| input.as_bytes());
match output {
Some(output) => {
let message = message.expect(&format!("{}: should decode but did not", name));
assert_eq!(message, output, "{}: decoded a wrong message", name);
},
None => {
message.err().expect(&format!("{}: should not decode but did", name));
},
}
}
}
/// Returns (test_name, input, hex, output).
fn test_closures() -> Vec<(&'static str, &'static str, bool, Option<&'static [u8]>)> {
vec![
("decode_no_hex_works", "Hello this is not hex", false, Some(b"Hello this is not hex")),
("decode_no_hex_with_hex_string_works", "0xffffffff", false, Some(b"0xffffffff")),
("decode_hex_works", "0x00112233", true, Some(&[0, 17, 34, 51])),
("decode_hex_without_prefix_works", "00112233", true, Some(&[0, 17, 34, 51])),
("decode_hex_uppercase_works", "0xaAbbCCDd", true, Some(&[170, 187, 204, 221])),
("decode_hex_wrong_len_errors", "0x0011223", true, None),
]
}
}
@@ -0,0 +1,67 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use clap::Args;
use sp_core::H256;
use std::str::FromStr;
fn parse_kx_secret(s: &str) -> Result<sc_mixnet::KxSecret, String> {
H256::from_str(s).map(H256::to_fixed_bytes).map_err(|err| err.to_string())
}
/// Parameters used to create the mixnet configuration.
#[derive(Debug, Clone, Args)]
pub struct MixnetParams {
/// Enable the mixnet service.
///
/// This will make the mixnet RPC methods available. If the node is running as a validator, it
/// will also attempt to register and operate as a mixnode.
#[arg(long)]
pub mixnet: bool,
/// The mixnet key-exchange secret to use in session 0.
///
/// Should be 64 hex characters, giving a 32-byte secret.
///
/// WARNING: Secrets provided as command-line arguments are easily exposed. Use of this option
/// should be limited to development and testing.
#[arg(long, value_name = "SECRET", value_parser = parse_kx_secret)]
pub mixnet_session_0_kx_secret: Option<sc_mixnet::KxSecret>,
}
impl MixnetParams {
/// Returns the mixnet configuration, or `None` if the mixnet is disabled.
pub fn config(&self, is_authority: bool) -> Option<sc_mixnet::Config> {
self.mixnet.then(|| {
let mut config = sc_mixnet::Config {
core: sc_mixnet::CoreConfig {
session_0_kx_secret: self.mixnet_session_0_kx_secret,
..Default::default()
},
..Default::default()
};
if !is_authority {
// Only authorities can be mixnodes; don't attempt to register
config.substrate.register = false;
// Only mixnodes need to allow connections from non-mixnodes
config.substrate.num_gateway_slots = 0;
}
config
})
}
}
+203
View File
@@ -0,0 +1,203 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
mod database_params;
mod import_params;
mod keystore_params;
mod message_params;
mod mixnet_params;
mod network_params;
mod node_key_params;
mod offchain_worker_params;
mod prometheus_params;
mod pruning_params;
mod rpc_params;
mod runtime_params;
mod shared_params;
mod telemetry_params;
mod transaction_pool_params;
use crate::arg_enums::{CryptoScheme, OutputType};
use clap::Args;
use sc_service::config::{IpNetwork, RpcBatchRequestConfig};
use sp_core::crypto::{Ss58AddressFormat, Ss58AddressFormatRegistry};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, NumberFor},
};
use std::{fmt::Debug, str::FromStr};
pub use crate::params::{
database_params::*, import_params::*, keystore_params::*, message_params::*, mixnet_params::*,
network_params::*, node_key_params::*, offchain_worker_params::*, prometheus_params::*,
pruning_params::*, rpc_params::*, runtime_params::*, shared_params::*, telemetry_params::*,
transaction_pool_params::*,
};
/// Parse Ss58AddressFormat
pub fn parse_ss58_address_format(x: &str) -> Result<Ss58AddressFormat, String> {
match Ss58AddressFormatRegistry::try_from(x) {
Ok(format_registry) => Ok(format_registry.into()),
Err(_) => Err(format!(
"Unable to parse variant. Known variants: {:?}",
Ss58AddressFormat::all_names()
)),
}
}
/// Wrapper type of `String` that holds an unsigned integer of arbitrary size, formatted as a
/// decimal.
#[derive(Debug, Clone)]
pub struct GenericNumber(String);
impl FromStr for GenericNumber {
type Err = String;
fn from_str(block_number: &str) -> Result<Self, Self::Err> {
if let Some(pos) = block_number.chars().position(|d| !d.is_digit(10)) {
Err(format!("Expected block number, found illegal digit at position: {}", pos))
} else {
Ok(Self(block_number.to_owned()))
}
}
}
impl GenericNumber {
/// Wrapper on top of `std::str::parse<N>` but with `Error` as a `String`
///
/// See `https://doc.rust-lang.org/std/primitive.str.html#method.parse` for more elaborate
/// documentation.
pub fn parse<N>(&self) -> Result<N, String>
where
N: FromStr,
N::Err: std::fmt::Debug,
{
FromStr::from_str(&self.0).map_err(|e| format!("Failed to parse block number: {:?}", e))
}
}
/// Wrapper type that is either a `Hash` or the number of a `Block`.
#[derive(Debug, Clone)]
pub struct BlockNumberOrHash(String);
impl FromStr for BlockNumberOrHash {
type Err = String;
fn from_str(block_number: &str) -> Result<Self, Self::Err> {
if let Some(rest) = block_number.strip_prefix("0x") {
if let Some(pos) = rest.chars().position(|c| !c.is_ascii_hexdigit()) {
Err(format!(
"Expected block hash, found illegal hex character at position: {}",
2 + pos,
))
} else {
Ok(Self(block_number.into()))
}
} else {
GenericNumber::from_str(block_number).map(|v| Self(v.0))
}
}
}
impl BlockNumberOrHash {
/// Parse the inner value as `BlockId`.
pub fn parse<B: BlockT>(&self) -> Result<BlockId<B>, String>
where
<B::Hash as FromStr>::Err: std::fmt::Debug,
NumberFor<B>: FromStr,
<NumberFor<B> as FromStr>::Err: std::fmt::Debug,
{
if self.0.starts_with("0x") {
Ok(BlockId::Hash(
FromStr::from_str(&self.0[2..])
.map_err(|e| format!("Failed to parse block hash: {:?}", e))?,
))
} else {
GenericNumber(self.0.clone()).parse().map(BlockId::Number)
}
}
}
/// Optional flag for specifying crypto algorithm
#[derive(Debug, Clone, Args)]
pub struct CryptoSchemeFlag {
/// cryptography scheme
#[arg(long, value_name = "SCHEME", value_enum, ignore_case = true, default_value_t = CryptoScheme::Sr25519)]
pub scheme: CryptoScheme,
}
/// Optional flag for specifying output type
#[derive(Debug, Clone, Args)]
pub struct OutputTypeFlag {
/// output format
#[arg(long, value_name = "FORMAT", value_enum, ignore_case = true, default_value_t = OutputType::Text)]
pub output_type: OutputType,
}
/// Optional flag for specifying network scheme
#[derive(Debug, Clone, Args)]
pub struct NetworkSchemeFlag {
/// network address format
#[arg(
short = 'n',
long,
value_name = "NETWORK",
ignore_case = true,
value_parser = parse_ss58_address_format,
)]
pub network: Option<Ss58AddressFormat>,
}
#[cfg(test)]
mod tests {
use super::*;
type Header = sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>;
type Block = sp_runtime::generic::Block<Header, sp_runtime::OpaqueExtrinsic>;
#[test]
fn parse_block_number() {
let block_number_or_hash = BlockNumberOrHash::from_str("1234").unwrap();
let parsed = block_number_or_hash.parse::<Block>().unwrap();
assert_eq!(BlockId::Number(1234), parsed);
}
#[test]
fn parse_block_hash() {
let hash = sp_core::H256::default();
let hash_str = format!("{:?}", hash);
let block_number_or_hash = BlockNumberOrHash::from_str(&hash_str).unwrap();
let parsed = block_number_or_hash.parse::<Block>().unwrap();
assert_eq!(BlockId::Hash(hash), parsed);
}
#[test]
fn parse_block_hash_fails() {
assert_eq!(
"Expected block hash, found illegal hex character at position: 2",
BlockNumberOrHash::from_str("0xHello").unwrap_err(),
);
}
#[test]
fn parse_block_number_fails() {
assert_eq!(
"Expected block number, found illegal digit at position: 3",
BlockNumberOrHash::from_str("345Hello").unwrap_err(),
);
}
}
@@ -0,0 +1,342 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
arg_enums::{NetworkBackendType, SyncMode},
params::node_key_params::NodeKeyParams,
};
use clap::Args;
use sc_network::{
config::{
NetworkConfiguration, NodeKeyConfig, NonReservedPeerMode, SetConfig, TransportConfig,
DEFAULT_IDLE_CONNECTION_TIMEOUT,
},
multiaddr::Protocol,
};
use sc_service::{
config::{Multiaddr, MultiaddrWithPeerId},
ChainSpec, ChainType,
};
use std::{borrow::Cow, num::NonZeroUsize, path::PathBuf};
/// Parameters used to create the network configuration.
#[derive(Debug, Clone, Args)]
pub struct NetworkParams {
/// Specify a list of bootnodes.
#[arg(long, value_name = "ADDR", num_args = 1..)]
pub bootnodes: Vec<MultiaddrWithPeerId>,
/// Specify a list of reserved node addresses.
#[arg(long, value_name = "ADDR", num_args = 1..)]
pub reserved_nodes: Vec<MultiaddrWithPeerId>,
/// Whether to only synchronize the chain with reserved nodes.
///
/// Also disables automatic peer discovery.
/// TCP connections might still be established with non-reserved nodes.
/// In particular, if you are a validator your node might still connect to other
/// validator nodes and collator nodes regardless of whether they are defined as
/// reserved nodes.
#[arg(long)]
pub reserved_only: bool,
/// Public address that other nodes will use to connect to this node.
///
/// This can be used if there's a proxy in front of this node.
#[arg(long, value_name = "PUBLIC_ADDR", num_args = 1..)]
pub public_addr: Vec<Multiaddr>,
/// Listen on this multiaddress.
///
/// By default:
/// If `--validator` is passed: `/ip4/0.0.0.0/tcp/<port>` and `/ip6/[::]/tcp/<port>`.
/// Otherwise: `/ip4/0.0.0.0/tcp/<port>/ws` and `/ip6/[::]/tcp/<port>/ws`.
#[arg(long, value_name = "LISTEN_ADDR", num_args = 1..)]
pub listen_addr: Vec<Multiaddr>,
/// Specify p2p protocol TCP port.
#[arg(long, value_name = "PORT", conflicts_with_all = &[ "listen_addr" ])]
pub port: Option<u16>,
/// Always forbid connecting to private IPv4/IPv6 addresses.
///
/// The option doesn't apply to addresses passed with `--reserved-nodes` or
/// `--bootnodes`. Enabled by default for chains marked as "live" in their chain
/// specifications.
///
/// Address allocation for private networks is specified by
/// [RFC1918](https://tools.ietf.org/html/rfc1918)).
#[arg(long, alias = "no-private-ipv4", conflicts_with_all = &["allow_private_ip"])]
pub no_private_ip: bool,
/// Always accept connecting to private IPv4/IPv6 addresses.
///
/// Enabled by default for chains marked as "local" in their chain specifications,
/// or when `--dev` is passed.
///
/// Address allocation for private networks is specified by
/// [RFC1918](https://tools.ietf.org/html/rfc1918)).
#[arg(long, alias = "allow-private-ipv4", conflicts_with_all = &["no_private_ip"])]
pub allow_private_ip: bool,
/// Number of outgoing connections we're trying to maintain.
#[arg(long, value_name = "COUNT", default_value_t = 8)]
pub out_peers: u32,
/// Maximum number of inbound full nodes peers.
#[arg(long, value_name = "COUNT", default_value_t = 32)]
pub in_peers: u32,
/// Maximum number of inbound light nodes peers.
#[arg(long, value_name = "COUNT", default_value_t = 100)]
pub in_peers_light: u32,
/// Disable mDNS discovery (default: true).
///
/// By default, the network will use mDNS to discover other nodes on the
/// local network. This disables it. Automatically implied when using --dev.
#[arg(long)]
pub no_mdns: bool,
/// Maximum number of peers from which to ask for the same blocks in parallel.
///
/// This allows downloading announced blocks from multiple peers.
/// Decrease to save traffic and risk increased latency.
#[arg(long, value_name = "COUNT", default_value_t = 5)]
pub max_parallel_downloads: u32,
#[allow(missing_docs)]
#[clap(flatten)]
pub node_key_params: NodeKeyParams,
/// Enable peer discovery on local networks.
///
/// By default this option is `true` for `--dev` or when the chain type is
/// `Local`/`Development` and false otherwise.
#[arg(long)]
pub discover_local: bool,
/// Require iterative Kademlia DHT queries to use disjoint paths.
///
/// Disjoint paths increase resiliency in the presence of potentially adversarial nodes.
///
/// See the S/Kademlia paper for more information on the high level design as well as its
/// security improvements.
#[arg(long)]
pub kademlia_disjoint_query_paths: bool,
/// Kademlia replication factor.
///
/// Determines to how many closest peers a record is replicated to.
///
/// Discovery mechanism requires successful replication to all
/// `kademlia_replication_factor` peers to consider record successfully put.
#[arg(long, default_value = "20")]
pub kademlia_replication_factor: NonZeroUsize,
/// Join the IPFS network and serve transactions over bitswap protocol.
#[arg(long)]
pub ipfs_server: bool,
/// Blockchain syncing mode.
#[arg(
long,
value_enum,
value_name = "SYNC_MODE",
default_value_t = SyncMode::Full,
ignore_case = true,
verbatim_doc_comment
)]
pub sync: SyncMode,
/// Maximum number of blocks per request.
///
/// Try reducing this number from the default value if you have a slow network connection
/// and observe block requests timing out.
#[arg(long, value_name = "COUNT", default_value_t = 64)]
pub max_blocks_per_request: u32,
/// Network backend used for P2P networking.
///
/// Litep2p is a lightweight alternative to libp2p, that is designed to be more
/// efficient and easier to use. At the same time, litep2p brings performance
/// improvements and reduces the CPU usage significantly.
///
/// Libp2p is the old network backend, that may still be used for compatibility
/// reasons until the whole ecosystem is migrated to litep2p.
#[arg(
long,
value_enum,
value_name = "NETWORK_BACKEND",
default_value_t = NetworkBackendType::Litep2p,
ignore_case = true,
verbatim_doc_comment
)]
pub network_backend: NetworkBackendType,
}
impl NetworkParams {
/// Fill the given `NetworkConfiguration` by looking at the cli parameters.
pub fn network_config(
&self,
chain_spec: &Box<dyn ChainSpec>,
is_dev: bool,
is_validator: bool,
net_config_path: Option<PathBuf>,
client_id: &str,
node_name: &str,
node_key: NodeKeyConfig,
default_listen_port: u16,
) -> NetworkConfiguration {
let port = self.port.unwrap_or(default_listen_port);
let listen_addresses = if self.listen_addr.is_empty() {
if is_validator || is_dev {
vec![
Multiaddr::empty()
.with(Protocol::Ip6([0, 0, 0, 0, 0, 0, 0, 0].into()))
.with(Protocol::Tcp(port)),
Multiaddr::empty()
.with(Protocol::Ip4([0, 0, 0, 0].into()))
.with(Protocol::Tcp(port)),
]
} else {
vec![
Multiaddr::empty()
.with(Protocol::Ip6([0, 0, 0, 0, 0, 0, 0, 0].into()))
.with(Protocol::Tcp(port))
.with(Protocol::Ws(Cow::Borrowed("/"))),
Multiaddr::empty()
.with(Protocol::Ip4([0, 0, 0, 0].into()))
.with(Protocol::Tcp(port))
.with(Protocol::Ws(Cow::Borrowed("/"))),
]
}
} else {
self.listen_addr.clone()
};
let public_addresses = self.public_addr.clone();
let mut boot_nodes = chain_spec.boot_nodes().to_vec();
boot_nodes.extend(self.bootnodes.clone());
let chain_type = chain_spec.chain_type();
// Activate if the user explicitly requested local discovery, `--dev` is given or the
// chain type is `Local`/`Development`
let allow_non_globals_in_dht =
self.discover_local ||
is_dev || matches!(chain_type, ChainType::Local | ChainType::Development);
let allow_private_ip = match (self.allow_private_ip, self.no_private_ip) {
(true, true) => unreachable!("`*_private_ip` flags are mutually exclusive; qed"),
(true, false) => true,
(false, true) => false,
(false, false) =>
is_dev || matches!(chain_type, ChainType::Local | ChainType::Development),
};
NetworkConfiguration {
boot_nodes,
net_config_path,
default_peers_set: SetConfig {
in_peers: self.in_peers + self.in_peers_light,
out_peers: self.out_peers,
reserved_nodes: self.reserved_nodes.clone(),
non_reserved_mode: if self.reserved_only {
NonReservedPeerMode::Deny
} else {
NonReservedPeerMode::Accept
},
},
default_peers_set_num_full: self.in_peers + self.out_peers,
listen_addresses,
public_addresses,
node_key,
node_name: node_name.to_string(),
client_version: client_id.to_string(),
transport: TransportConfig::Normal {
enable_mdns: !is_dev && !self.no_mdns,
allow_private_ip,
},
idle_connection_timeout: DEFAULT_IDLE_CONNECTION_TIMEOUT,
max_parallel_downloads: self.max_parallel_downloads,
max_blocks_per_request: self.max_blocks_per_request,
min_peers_to_start_warp_sync: None,
enable_dht_random_walk: !self.reserved_only,
allow_non_globals_in_dht,
kademlia_disjoint_query_paths: self.kademlia_disjoint_query_paths,
kademlia_replication_factor: self.kademlia_replication_factor,
ipfs_server: self.ipfs_server,
sync_mode: self.sync.into(),
network_backend: self.network_backend.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[derive(Parser)]
struct Cli {
#[clap(flatten)]
network_params: NetworkParams,
}
#[test]
fn reserved_nodes_multiple_values_and_occurrences() {
let params = Cli::try_parse_from([
"",
"--reserved-nodes",
"/ip4/0.0.0.0/tcp/501/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS",
"/ip4/0.0.0.0/tcp/502/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS",
"--reserved-nodes",
"/ip4/0.0.0.0/tcp/503/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS",
])
.expect("Parses network params");
let expected = vec![
MultiaddrWithPeerId::try_from(
"/ip4/0.0.0.0/tcp/501/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS"
.to_string(),
)
.unwrap(),
MultiaddrWithPeerId::try_from(
"/ip4/0.0.0.0/tcp/502/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS"
.to_string(),
)
.unwrap(),
MultiaddrWithPeerId::try_from(
"/ip4/0.0.0.0/tcp/503/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS"
.to_string(),
)
.unwrap(),
];
assert_eq!(expected, params.network_params.reserved_nodes);
}
#[test]
fn sync_ignores_case() {
let params = Cli::try_parse_from(["", "--sync", "wArP"]).expect("Parses network params");
assert_eq!(SyncMode::Warp, params.network_params.sync);
}
}
@@ -0,0 +1,268 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use clap::Args;
use sc_network::config::{ed25519, NodeKeyConfig};
use sc_service::Role;
use sp_core::H256;
use std::{path::PathBuf, str::FromStr};
use crate::{arg_enums::NodeKeyType, error, Error};
/// The file name of the node's Ed25519 secret key inside the chain-specific
/// network config directory, if neither `--node-key` nor `--node-key-file`
/// is specified in combination with `--node-key-type=ed25519`.
pub(crate) const NODE_KEY_ED25519_FILE: &str = "secret_ed25519";
/// Parameters used to create the `NodeKeyConfig`, which determines the keypair
/// used for libp2p networking.
#[derive(Debug, Clone, Args)]
pub struct NodeKeyParams {
/// Secret key to use for p2p networking.
///
/// The value is a string that is parsed according to the choice of
/// `--node-key-type` as follows:
///
/// - `ed25519`: the value is parsed as a hex-encoded Ed25519 32 byte secret key (64 hex
/// chars)
///
/// The value of this option takes precedence over `--node-key-file`.
///
/// WARNING: Secrets provided as command-line arguments are easily exposed.
/// Use of this option should be limited to development and testing. To use
/// an externally managed secret key, use `--node-key-file` instead.
#[arg(long, value_name = "KEY")]
pub node_key: Option<String>,
/// Crypto primitive to use for p2p networking.
///
/// The secret key of the node is obtained as follows:
///
/// - If the `--node-key` option is given, the value is parsed as a secret key according to the
/// type. See the documentation for `--node-key`.
///
/// - If the `--node-key-file` option is given, the secret key is read from the specified file.
/// See the documentation for `--node-key-file`.
///
/// - Otherwise, the secret key is read from a file with a predetermined, type-specific name
/// from the chain-specific network config directory inside the base directory specified by
/// `--base-dir`. If this file does not exist, it is created with a newly generated secret
/// key of the chosen type.
///
/// The node's secret key determines the corresponding public key and hence the
/// node's peer ID in the context of libp2p.
#[arg(long, value_name = "TYPE", value_enum, ignore_case = true, default_value_t = NodeKeyType::Ed25519)]
pub node_key_type: NodeKeyType,
/// File from which to read the node's secret key to use for p2p networking.
///
/// The contents of the file are parsed according to the choice of `--node-key-type`
/// as follows:
///
/// - `ed25519`: the file must contain an unencoded 32 byte or hex encoded Ed25519 secret key.
///
/// If the file does not exist, it is created with a newly generated secret key of
/// the chosen type.
#[arg(long, value_name = "FILE")]
pub node_key_file: Option<PathBuf>,
/// Forces key generation if node-key-file file does not exist.
///
/// This is an unsafe feature for production networks, because as an active authority
/// other authorities may depend on your node having a stable identity and they might
/// not being able to reach you if your identity changes after entering the active set.
///
/// For minimal node downtime if no custom `node-key-file` argument is provided
/// the network-key is usually persisted accross nodes restarts,
/// in the `network` folder from directory provided in `--base-path`
///
/// Warning!! If you ever run the node with this argument, make sure
/// you remove it for the subsequent restarts.
#[arg(long)]
pub unsafe_force_node_key_generation: bool,
}
impl NodeKeyParams {
/// Create a `NodeKeyConfig` from the given `NodeKeyParams` in the context
/// of an optional network config storage directory.
pub fn node_key(
&self,
net_config_dir: &PathBuf,
role: Role,
is_dev: bool,
) -> error::Result<NodeKeyConfig> {
Ok(match self.node_key_type {
NodeKeyType::Ed25519 => {
let secret = if let Some(node_key) = self.node_key.as_ref() {
parse_ed25519_secret(node_key)?
} else {
let key_path = self
.node_key_file
.clone()
.unwrap_or_else(|| net_config_dir.join(NODE_KEY_ED25519_FILE));
if !self.unsafe_force_node_key_generation &&
role.is_authority() &&
!is_dev && !key_path.exists()
{
return Err(Error::NetworkKeyNotFound(key_path));
}
sc_network::config::Secret::File(key_path)
};
NodeKeyConfig::Ed25519(secret)
},
})
}
}
/// Create an error caused by an invalid node key argument.
fn invalid_node_key(e: impl std::fmt::Display) -> error::Error {
error::Error::Input(format!("Invalid node key: {}", e))
}
/// Parse a Ed25519 secret key from a hex string into a `sc_network::Secret`.
fn parse_ed25519_secret(hex: &str) -> error::Result<sc_network::config::Ed25519Secret> {
H256::from_str(hex).map_err(invalid_node_key).and_then(|bytes| {
ed25519::SecretKey::try_from_bytes(bytes)
.map(sc_network::config::Secret::Input)
.map_err(invalid_node_key)
})
}
#[cfg(test)]
mod tests {
use super::*;
use clap::ValueEnum;
use sc_network::config::ed25519;
use std::fs::{self, File};
use tempfile::TempDir;
#[test]
fn test_node_key_config_input() {
fn secret_input(net_config_dir: &PathBuf) -> error::Result<()> {
NodeKeyType::value_variants().iter().try_for_each(|t| {
let node_key_type = *t;
let sk = match node_key_type {
NodeKeyType::Ed25519 => ed25519::SecretKey::generate().as_ref().to_vec(),
};
let params = NodeKeyParams {
node_key_type,
node_key: Some(format!("{:x}", H256::from_slice(sk.as_ref()))),
node_key_file: None,
unsafe_force_node_key_generation: false,
};
params.node_key(net_config_dir, Role::Authority, false).and_then(|c| match c {
NodeKeyConfig::Ed25519(sc_network::config::Secret::Input(ref ski))
if node_key_type == NodeKeyType::Ed25519 && &sk[..] == ski.as_ref() =>
Ok(()),
_ => Err(error::Error::Input("Unexpected node key config".into())),
})
})
}
assert!(secret_input(&PathBuf::from_str("x").unwrap()).is_ok());
}
#[test]
fn test_node_key_config_file() {
fn check_key(file: PathBuf, key: &ed25519::SecretKey) {
let params = NodeKeyParams {
node_key_type: NodeKeyType::Ed25519,
node_key: None,
node_key_file: Some(file),
unsafe_force_node_key_generation: false,
};
let node_key = params
.node_key(&PathBuf::from("not-used"), Role::Authority, false)
.expect("Creates node key config")
.into_keypair()
.expect("Creates node key pair");
if node_key.secret().as_ref() != key.as_ref() {
panic!("Invalid key")
}
}
let tmp = tempfile::Builder::new().prefix("alice").tempdir().expect("Creates tempfile");
let file = tmp.path().join("mysecret").to_path_buf();
let key = ed25519::SecretKey::generate();
fs::write(&file, array_bytes::bytes2hex("", key.as_ref())).expect("Writes secret key");
check_key(file.clone(), &key);
fs::write(&file, &key).expect("Writes secret key");
check_key(file.clone(), &key);
}
#[test]
fn test_node_key_config_default() {
fn with_def_params<F>(f: F, unsafe_force_node_key_generation: bool) -> error::Result<()>
where
F: Fn(NodeKeyParams) -> error::Result<()>,
{
NodeKeyType::value_variants().iter().try_for_each(|t| {
let node_key_type = *t;
f(NodeKeyParams {
node_key_type,
node_key: None,
node_key_file: None,
unsafe_force_node_key_generation,
})
})
}
fn some_config_dir(
net_config_dir: &PathBuf,
unsafe_force_node_key_generation: bool,
role: Role,
is_dev: bool,
) -> error::Result<()> {
with_def_params(
|params| {
let dir = PathBuf::from(net_config_dir.clone());
let typ = params.node_key_type;
params.node_key(net_config_dir, role, is_dev).and_then(move |c| match c {
NodeKeyConfig::Ed25519(sc_network::config::Secret::File(ref f))
if typ == NodeKeyType::Ed25519 &&
f == &dir.join(NODE_KEY_ED25519_FILE) =>
Ok(()),
_ => Err(error::Error::Input("Unexpected node key config".into())),
})
},
unsafe_force_node_key_generation,
)
}
assert!(some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Full, false).is_ok());
assert!(
some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, true).is_ok()
);
assert!(
some_config_dir(&PathBuf::from_str("x").unwrap(), true, Role::Authority, false).is_ok()
);
assert!(matches!(
some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, false),
Err(Error::NetworkKeyNotFound(_))
));
let tempdir = TempDir::new().unwrap();
let _file = File::create(tempdir.path().join(NODE_KEY_ED25519_FILE)).unwrap();
assert!(some_config_dir(&tempdir.path().into(), false, Role::Authority, false).is_ok());
}
}
@@ -0,0 +1,65 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Offchain worker related configuration parameters.
//!
//! A subset of configuration parameters which are relevant to
//! the inner working of offchain workers. The usage is solely
//! targeted at handling input parameter parsing providing
//! a reasonable abstraction.
use clap::{ArgAction, Args};
use sc_network::config::Role;
use sc_service::config::OffchainWorkerConfig;
use crate::{error, OffchainWorkerEnabled};
/// Offchain worker related parameters.
#[derive(Debug, Clone, Args)]
pub struct OffchainWorkerParams {
/// Execute offchain workers on every block.
#[arg(
long = "offchain-worker",
value_name = "ENABLED",
value_enum,
ignore_case = true,
default_value_t = OffchainWorkerEnabled::WhenAuthority
)]
pub enabled: OffchainWorkerEnabled,
/// Enable offchain indexing API.
///
/// Allows the runtime to write directly to offchain workers DB during block import.
#[arg(long = "enable-offchain-indexing", value_name = "ENABLE_OFFCHAIN_INDEXING", default_value_t = false, action = ArgAction::Set)]
pub indexing_enabled: bool,
}
impl OffchainWorkerParams {
/// Load spec to `Configuration` from `OffchainWorkerParams` and spec factory.
pub fn offchain_worker(&self, role: &Role) -> error::Result<OffchainWorkerConfig> {
let enabled = match (&self.enabled, role) {
(OffchainWorkerEnabled::WhenAuthority, Role::Authority { .. }) => true,
(OffchainWorkerEnabled::Always, _) => true,
(OffchainWorkerEnabled::Never, _) => false,
(OffchainWorkerEnabled::WhenAuthority, _) => false,
};
let indexing_enabled = self.indexing_enabled;
Ok(OffchainWorkerConfig { enabled, indexing_enabled })
}
}
@@ -0,0 +1,63 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use clap::Args;
use sc_service::config::PrometheusConfig;
use std::net::{Ipv4Addr, SocketAddr};
/// Parameters used to config prometheus.
#[derive(Debug, Clone, Args)]
pub struct PrometheusParams {
/// Specify Prometheus exporter TCP Port.
#[arg(long, value_name = "PORT")]
pub prometheus_port: Option<u16>,
/// Expose Prometheus exporter on all interfaces.
///
/// Default is local.
#[arg(long)]
pub prometheus_external: bool,
/// Do not expose a Prometheus exporter endpoint.
///
/// Prometheus metric endpoint is enabled by default.
#[arg(long)]
pub no_prometheus: bool,
}
impl PrometheusParams {
/// Creates [`PrometheusConfig`].
pub fn prometheus_config(
&self,
default_listen_port: u16,
chain_id: String,
) -> Option<PrometheusConfig> {
if self.no_prometheus {
None
} else {
let interface =
if self.prometheus_external { Ipv4Addr::UNSPECIFIED } else { Ipv4Addr::LOCALHOST };
Some(PrometheusConfig::new_with_default_registry(
SocketAddr::new(
interface.into(),
self.prometheus_port.unwrap_or(default_listen_port),
),
chain_id,
))
}
}
}
@@ -0,0 +1,165 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::error;
use clap::Args;
use sc_service::{BlocksPruning, PruningMode};
/// Parameters to define the pruning mode
#[derive(Debug, Clone, Args)]
pub struct PruningParams {
/// Specify the state pruning mode.
///
/// This mode specifies when the block's state (ie, storage)
/// should be pruned (ie, removed) from the database.
/// This setting can only be set on the first creation of the database. Every subsequent run
/// will load the pruning mode from the database and will error if the stored mode doesn't
/// match this CLI value. It is fine to drop this CLI flag for subsequent runs. The only
/// exception is that `NUMBER` can change between subsequent runs (increasing it will not
/// lead to restoring pruned state).
///
/// Possible values:
///
/// - archive: Keep the data of all blocks.
///
/// - archive-canonical: Keep only the data of finalized blocks.
///
/// - NUMBER: Keep the data of the last NUMBER of finalized blocks.
///
/// [default: 256]
#[arg(alias = "pruning", long, value_name = "PRUNING_MODE")]
pub state_pruning: Option<DatabasePruningMode>,
/// Specify the blocks pruning mode.
///
/// This mode specifies when the block's body (including justifications)
/// should be pruned (ie, removed) from the database.
///
/// Possible values:
///
/// - archive: Keep the data of all blocks.
///
/// - archive-canonical: Keep only the data of finalized blocks.
///
/// - NUMBER: Keep the data of the last NUMBER of finalized blocks.
#[arg(
alias = "keep-blocks",
long,
value_name = "PRUNING_MODE",
default_value = "archive-canonical"
)]
pub blocks_pruning: DatabasePruningMode,
}
impl PruningParams {
/// Get the pruning value from the parameters
pub fn state_pruning(&self) -> error::Result<Option<PruningMode>> {
Ok(self.state_pruning.map(|v| v.into()))
}
/// Get the block pruning value from the parameters
pub fn blocks_pruning(&self) -> error::Result<BlocksPruning> {
Ok(self.blocks_pruning.into())
}
}
/// Specifies the pruning mode of the database.
///
/// This specifies when the block's data (either state via `--state-pruning`
/// or body via `--blocks-pruning`) should be pruned (ie, removed) from
/// the database.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DatabasePruningMode {
/// Keep the data of all blocks.
Archive,
/// Keep only the data of finalized blocks.
ArchiveCanonical,
/// Keep the data of the last number of finalized blocks.
Custom(u32),
}
impl std::str::FromStr for DatabasePruningMode {
type Err = String;
fn from_str(input: &str) -> Result<Self, Self::Err> {
match input {
"archive" => Ok(Self::Archive),
"archive-canonical" => Ok(Self::ArchiveCanonical),
bc => bc
.parse()
.map_err(|_| "Invalid pruning mode specified".to_string())
.map(Self::Custom),
}
}
}
impl Into<PruningMode> for DatabasePruningMode {
fn into(self) -> PruningMode {
match self {
DatabasePruningMode::Archive => PruningMode::ArchiveAll,
DatabasePruningMode::ArchiveCanonical => PruningMode::ArchiveCanonical,
DatabasePruningMode::Custom(n) => PruningMode::blocks_pruning(n),
}
}
}
impl Into<BlocksPruning> for DatabasePruningMode {
fn into(self) -> BlocksPruning {
match self {
DatabasePruningMode::Archive => BlocksPruning::KeepAll,
DatabasePruningMode::ArchiveCanonical => BlocksPruning::KeepFinalized,
DatabasePruningMode::Custom(n) => BlocksPruning::Some(n),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[derive(Parser)]
struct Cli {
#[clap(flatten)]
pruning: PruningParams,
}
#[test]
fn pruning_params_parse_works() {
let Cli { pruning } =
Cli::parse_from(["", "--state-pruning=1000", "--blocks-pruning=1000"]);
assert!(matches!(pruning.state_pruning, Some(DatabasePruningMode::Custom(1000))));
assert!(matches!(pruning.blocks_pruning, DatabasePruningMode::Custom(1000)));
let Cli { pruning } =
Cli::parse_from(["", "--state-pruning=archive", "--blocks-pruning=archive"]);
assert!(matches!(dbg!(pruning.state_pruning), Some(DatabasePruningMode::Archive)));
assert!(matches!(pruning.blocks_pruning, DatabasePruningMode::Archive));
let Cli { pruning } = Cli::parse_from([
"",
"--state-pruning=archive-canonical",
"--blocks-pruning=archive-canonical",
]);
assert!(matches!(dbg!(pruning.state_pruning), Some(DatabasePruningMode::ArchiveCanonical)));
assert!(matches!(pruning.blocks_pruning, DatabasePruningMode::ArchiveCanonical));
}
}
@@ -0,0 +1,681 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
arg_enums::{Cors, RpcMethods},
params::{IpNetwork, RpcBatchRequestConfig},
RPC_DEFAULT_MAX_CONNECTIONS, RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB,
RPC_DEFAULT_MAX_SUBS_PER_CONN, RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN,
};
use clap::Args;
use std::{
net::{Ipv4Addr, Ipv6Addr, SocketAddr},
num::NonZeroU32,
};
const RPC_LISTEN_ADDR: &str = "listen-addr";
const RPC_CORS: &str = "cors";
const RPC_MAX_CONNS: &str = "max-connections";
const RPC_MAX_REQUEST_SIZE: &str = "max-request-size";
const RPC_MAX_RESPONSE_SIZE: &str = "max-response-size";
const RPC_MAX_SUBS_PER_CONN: &str = "max-subscriptions-per-connection";
const RPC_MAX_BUF_CAP_PER_CONN: &str = "max-buffer-capacity-per-connection";
const RPC_RATE_LIMIT: &str = "rate-limit";
const RPC_RATE_LIMIT_TRUST_PROXY_HEADERS: &str = "rate-limit-trust-proxy-headers";
const RPC_RATE_LIMIT_WHITELISTED_IPS: &str = "rate-limit-whitelisted-ips";
const RPC_RETRY_RANDOM_PORT: &str = "retry-random-port";
const RPC_METHODS: &str = "methods";
const RPC_OPTIONAL: &str = "optional";
const RPC_DISABLE_BATCH: &str = "disable-batch-requests";
const RPC_BATCH_LIMIT: &str = "max-batch-request-len";
/// Parameters of RPC.
#[derive(Debug, Clone, Args)]
pub struct RpcParams {
/// Listen to all RPC interfaces (default: local).
///
/// Not all RPC methods are safe to be exposed publicly.
///
/// Use an RPC proxy server to filter out dangerous methods. More details:
/// <https://docs.pezkuwichain.io/build/remote-procedure-calls/#public-rpc-interfaces>.
///
/// Use `--unsafe-rpc-external` to suppress the warning if you understand the risks.
#[arg(long)]
pub rpc_external: bool,
/// Listen to all RPC interfaces.
///
/// Same as `--rpc-external`.
#[arg(long)]
pub unsafe_rpc_external: bool,
/// RPC methods to expose.
#[arg(
long,
value_name = "METHOD SET",
value_enum,
ignore_case = true,
default_value_t = RpcMethods::Auto,
verbatim_doc_comment
)]
pub rpc_methods: RpcMethods,
/// RPC rate limiting (calls/minute) for each connection.
///
/// This is disabled by default.
///
/// For example `--rpc-rate-limit 10` will maximum allow
/// 10 calls per minute per connection.
#[arg(long)]
pub rpc_rate_limit: Option<NonZeroU32>,
/// Disable RPC rate limiting for certain ip addresses.
///
/// Each IP address must be in CIDR notation such as `1.2.3.4/24`.
#[arg(long, num_args = 1..)]
pub rpc_rate_limit_whitelisted_ips: Vec<IpNetwork>,
/// Trust proxy headers for disable rate limiting.
///
/// By default the rpc server will not trust headers such `X-Real-IP`, `X-Forwarded-For` and
/// `Forwarded` and this option will make the rpc server to trust these headers.
///
/// For instance this may be secure if the rpc server is behind a reverse proxy and that the
/// proxy always sets these headers.
#[arg(long)]
pub rpc_rate_limit_trust_proxy_headers: bool,
/// Set the maximum RPC request payload size for both HTTP and WS in megabytes.
#[arg(long, default_value_t = RPC_DEFAULT_MAX_REQUEST_SIZE_MB)]
pub rpc_max_request_size: u32,
/// Set the maximum RPC response payload size for both HTTP and WS in megabytes.
#[arg(long, default_value_t = RPC_DEFAULT_MAX_RESPONSE_SIZE_MB)]
pub rpc_max_response_size: u32,
/// Set the maximum concurrent subscriptions per connection.
#[arg(long, default_value_t = RPC_DEFAULT_MAX_SUBS_PER_CONN)]
pub rpc_max_subscriptions_per_connection: u32,
/// Specify JSON-RPC server TCP port.
#[arg(long, value_name = "PORT")]
pub rpc_port: Option<u16>,
/// EXPERIMENTAL: Specify the JSON-RPC server interface and this option which can be enabled
/// several times if you want expose several RPC interfaces with different configurations.
///
/// The format for this option is:
/// `--experimental-rpc-endpoint" listen-addr=<ip:port>,<key=value>,..."` where each option is
/// separated by a comma and `listen-addr` is the only required param.
///
/// The following options are available:
/// • listen-addr: The socket address (ip:port) to listen on. Be careful to not expose the
/// server to the public internet unless you know what you're doing. (required)
/// • disable-batch-requests: Disable batch requests (optional)
/// • max-connections: The maximum number of concurrent connections that the server will
/// accept (optional)
/// • max-request-size: The maximum size of a request body in megabytes (optional)
/// • max-response-size: The maximum size of a response body in megabytes (optional)
/// • max-subscriptions-per-connection: The maximum number of subscriptions per connection
/// (optional)
/// • max-buffer-capacity-per-connection: The maximum buffer capacity per connection
/// (optional)
/// • max-batch-request-len: The maximum number of requests in a batch (optional)
/// • cors: The CORS allowed origins, this can enabled more than once (optional)
/// • methods: Which RPC methods to allow, valid values are "safe", "unsafe" and "auto"
/// (optional)
/// • optional: If the listen address is optional i.e the interface is not required to be
/// available For example this may be useful if some platforms doesn't support ipv6
/// (optional)
/// • rate-limit: The rate limit in calls per minute for each connection (optional)
/// • rate-limit-trust-proxy-headers: Trust proxy headers for disable rate limiting (optional)
/// • rate-limit-whitelisted-ips: Disable rate limiting for certain ip addresses, this can be
/// enabled more than once (optional) • retry-random-port: If the port is already in use,
/// retry with a random port (optional)
///
/// Use with care, this flag is unstable and subject to change.
#[arg(
long,
num_args = 1..,
verbatim_doc_comment,
conflicts_with_all = &["rpc_external", "unsafe_rpc_external", "rpc_port", "rpc_cors", "rpc_rate_limit_trust_proxy_headers", "rpc_rate_limit", "rpc_rate_limit_whitelisted_ips", "rpc_message_buffer_capacity_per_connection", "rpc_disable_batch_requests", "rpc_max_subscriptions_per_connection", "rpc_max_request_size", "rpc_max_response_size"]
)]
pub experimental_rpc_endpoint: Vec<RpcEndpoint>,
/// Maximum number of RPC server connections.
#[arg(long, value_name = "COUNT", default_value_t = RPC_DEFAULT_MAX_CONNECTIONS)]
pub rpc_max_connections: u32,
/// The number of messages the RPC server is allowed to keep in memory.
///
/// If the buffer becomes full then the server will not process
/// new messages until the connected client start reading the
/// underlying messages.
///
/// This applies per connection which includes both
/// JSON-RPC methods calls and subscriptions.
#[arg(long, default_value_t = RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN)]
pub rpc_message_buffer_capacity_per_connection: u32,
/// Disable RPC batch requests
#[arg(long, alias = "rpc_no_batch_requests", conflicts_with_all = &["rpc_max_batch_request_len"])]
pub rpc_disable_batch_requests: bool,
/// Limit the max length per RPC batch request
#[arg(long, conflicts_with_all = &["rpc_disable_batch_requests"], value_name = "LEN")]
pub rpc_max_batch_request_len: Option<u32>,
/// Specify browser *origins* allowed to access the HTTP & WS RPC servers.
///
/// A comma-separated list of origins (protocol://domain or special `null`
/// value). Value of `all` will disable origin validation. Default is to
/// allow localhost and <https://polkadot.js.org> origins. When running in
/// `--dev` mode the default is to allow all origins.
#[arg(long, value_name = "ORIGINS")]
pub rpc_cors: Option<Cors>,
}
impl RpcParams {
/// Returns the RPC CORS configuration.
pub fn rpc_cors(&self, is_dev: bool) -> crate::Result<Option<Vec<String>>> {
Ok(self
.rpc_cors
.clone()
.unwrap_or_else(|| {
if is_dev {
log::warn!("Running in --dev mode, RPC CORS has been disabled.");
Cors::All
} else {
Cors::List(vec![
"http://localhost:*".into(),
"http://127.0.0.1:*".into(),
"https://localhost:*".into(),
"https://127.0.0.1:*".into(),
"https://polkadot.js.org".into(),
])
}
})
.into())
}
/// Returns the RPC endpoints.
pub fn rpc_addr(
&self,
is_dev: bool,
is_validator: bool,
default_listen_port: u16,
) -> crate::Result<Option<Vec<RpcEndpoint>>> {
if !self.experimental_rpc_endpoint.is_empty() {
for endpoint in &self.experimental_rpc_endpoint {
// Technically, `0.0.0.0` isn't a public IP address, but it's a way to listen on
// all interfaces. Thus, we consider it as a public endpoint and warn about it.
if endpoint.rpc_methods == RpcMethods::Unsafe && endpoint.is_global() ||
endpoint.listen_addr.ip().is_unspecified()
{
eprintln!(
"It isn't safe to expose RPC publicly without a proxy server that filters \
available set of RPC methods."
);
}
}
return Ok(Some(self.experimental_rpc_endpoint.clone()));
}
let (ipv4, ipv6) = rpc_interface(
self.rpc_external,
self.unsafe_rpc_external,
self.rpc_methods,
is_validator,
)?;
let cors = self.rpc_cors(is_dev)?;
let port = self.rpc_port.unwrap_or(default_listen_port);
Ok(Some(vec![
RpcEndpoint {
batch_config: self.rpc_batch_config()?,
max_connections: self.rpc_max_connections,
listen_addr: SocketAddr::new(std::net::IpAddr::V4(ipv4), port),
rpc_methods: self.rpc_methods,
rate_limit: self.rpc_rate_limit,
rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers,
rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips.clone(),
max_payload_in_mb: self.rpc_max_request_size,
max_payload_out_mb: self.rpc_max_response_size,
max_subscriptions_per_connection: self.rpc_max_subscriptions_per_connection,
max_buffer_capacity_per_connection: self.rpc_message_buffer_capacity_per_connection,
cors: cors.clone(),
retry_random_port: true,
is_optional: false,
},
RpcEndpoint {
batch_config: self.rpc_batch_config()?,
max_connections: self.rpc_max_connections,
listen_addr: SocketAddr::new(std::net::IpAddr::V6(ipv6), port),
rpc_methods: self.rpc_methods,
rate_limit: self.rpc_rate_limit,
rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers,
rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips.clone(),
max_payload_in_mb: self.rpc_max_request_size,
max_payload_out_mb: self.rpc_max_response_size,
max_subscriptions_per_connection: self.rpc_max_subscriptions_per_connection,
max_buffer_capacity_per_connection: self.rpc_message_buffer_capacity_per_connection,
cors: cors.clone(),
retry_random_port: true,
is_optional: true,
},
]))
}
/// Returns the configuration for batch RPC requests.
pub fn rpc_batch_config(&self) -> crate::Result<RpcBatchRequestConfig> {
let cfg = if self.rpc_disable_batch_requests {
RpcBatchRequestConfig::Disabled
} else if let Some(l) = self.rpc_max_batch_request_len {
RpcBatchRequestConfig::Limit(l)
} else {
RpcBatchRequestConfig::Unlimited
};
Ok(cfg)
}
}
fn rpc_interface(
is_external: bool,
is_unsafe_external: bool,
rpc_methods: RpcMethods,
is_validator: bool,
) -> crate::Result<(Ipv4Addr, Ipv6Addr)> {
if is_external && is_validator && rpc_methods != RpcMethods::Unsafe {
return Err(crate::Error::Input(
"--rpc-external option shouldn't be used if the node is running as \
a validator. Use `--unsafe-rpc-external` or `--rpc-methods=unsafe` if you understand \
the risks. See the options description for more information."
.to_owned(),
));
}
if is_external || is_unsafe_external {
if rpc_methods == RpcMethods::Unsafe {
eprintln!(
"It isn't safe to expose RPC publicly without a proxy server that filters \
available set of RPC methods."
);
}
Ok((Ipv4Addr::UNSPECIFIED, Ipv6Addr::UNSPECIFIED))
} else {
Ok((Ipv4Addr::LOCALHOST, Ipv6Addr::LOCALHOST))
}
}
/// Represent a single RPC endpoint with its configuration.
#[derive(Debug, Clone)]
pub struct RpcEndpoint {
/// Listen address.
pub listen_addr: SocketAddr,
/// Batch request configuration.
pub batch_config: RpcBatchRequestConfig,
/// Maximum number of connections.
pub max_connections: u32,
/// Maximum inbound payload size in MB.
pub max_payload_in_mb: u32,
/// Maximum outbound payload size in MB.
pub max_payload_out_mb: u32,
/// Maximum number of subscriptions per connection.
pub max_subscriptions_per_connection: u32,
/// Maximum buffer capacity per connection.
pub max_buffer_capacity_per_connection: u32,
/// Rate limit per minute.
pub rate_limit: Option<NonZeroU32>,
/// Whether to trust proxy headers for rate limiting.
pub rate_limit_trust_proxy_headers: bool,
/// Whitelisted IPs for rate limiting.
pub rate_limit_whitelisted_ips: Vec<IpNetwork>,
/// CORS.
pub cors: Option<Vec<String>>,
/// RPC methods to expose.
pub rpc_methods: RpcMethods,
/// Whether it's an optional listening address i.e, it's ignored if it fails to bind.
/// For example substrate tries to bind both ipv4 and ipv6 addresses but some platforms
/// may not support ipv6.
pub is_optional: bool,
/// Whether to retry with a random port if the provided port is already in use.
pub retry_random_port: bool,
}
impl std::str::FromStr for RpcEndpoint {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut listen_addr = None;
let mut max_connections = None;
let mut max_payload_in_mb = None;
let mut max_payload_out_mb = None;
let mut max_subscriptions_per_connection = None;
let mut max_buffer_capacity_per_connection = None;
let mut cors: Option<Vec<String>> = None;
let mut rpc_methods = None;
let mut is_optional = None;
let mut disable_batch_requests = None;
let mut max_batch_request_len = None;
let mut rate_limit = None;
let mut rate_limit_trust_proxy_headers = None;
let mut rate_limit_whitelisted_ips = Vec::new();
let mut retry_random_port = None;
for input in s.split(',') {
let (key, val) = input.trim().split_once('=').ok_or_else(|| invalid_input(input))?;
let key = key.trim();
let val = val.trim();
match key {
RPC_LISTEN_ADDR => {
if listen_addr.is_some() {
return Err(only_once_err(RPC_LISTEN_ADDR));
}
let val: SocketAddr =
val.parse().map_err(|_| invalid_value(RPC_LISTEN_ADDR, &val))?;
listen_addr = Some(val);
},
RPC_CORS => {
if val.is_empty() {
return Err(invalid_value(RPC_CORS, &val));
}
if let Some(cors) = cors.as_mut() {
cors.push(val.to_string());
} else {
cors = Some(vec![val.to_string()]);
}
},
RPC_MAX_CONNS => {
if max_connections.is_some() {
return Err(only_once_err(RPC_MAX_CONNS));
}
let val = val.parse().map_err(|_| invalid_value(RPC_MAX_CONNS, &val))?;
max_connections = Some(val);
},
RPC_MAX_REQUEST_SIZE => {
if max_payload_in_mb.is_some() {
return Err(only_once_err(RPC_MAX_REQUEST_SIZE));
}
let val =
val.parse().map_err(|_| invalid_value(RPC_MAX_RESPONSE_SIZE, &val))?;
max_payload_in_mb = Some(val);
},
RPC_MAX_RESPONSE_SIZE => {
if max_payload_out_mb.is_some() {
return Err(only_once_err(RPC_MAX_RESPONSE_SIZE));
}
let val =
val.parse().map_err(|_| invalid_value(RPC_MAX_RESPONSE_SIZE, &val))?;
max_payload_out_mb = Some(val);
},
RPC_MAX_SUBS_PER_CONN => {
if max_subscriptions_per_connection.is_some() {
return Err(only_once_err(RPC_MAX_SUBS_PER_CONN));
}
let val =
val.parse().map_err(|_| invalid_value(RPC_MAX_SUBS_PER_CONN, &val))?;
max_subscriptions_per_connection = Some(val);
},
RPC_MAX_BUF_CAP_PER_CONN => {
if max_buffer_capacity_per_connection.is_some() {
return Err(only_once_err(RPC_MAX_BUF_CAP_PER_CONN));
}
let val =
val.parse().map_err(|_| invalid_value(RPC_MAX_BUF_CAP_PER_CONN, &val))?;
max_buffer_capacity_per_connection = Some(val);
},
RPC_RATE_LIMIT => {
if rate_limit.is_some() {
return Err(only_once_err("rate-limit"));
}
let val = val.parse().map_err(|_| invalid_value(RPC_RATE_LIMIT, &val))?;
rate_limit = Some(val);
},
RPC_RATE_LIMIT_TRUST_PROXY_HEADERS => {
if rate_limit_trust_proxy_headers.is_some() {
return Err(only_once_err(RPC_RATE_LIMIT_TRUST_PROXY_HEADERS));
}
let val = val
.parse()
.map_err(|_| invalid_value(RPC_RATE_LIMIT_TRUST_PROXY_HEADERS, &val))?;
rate_limit_trust_proxy_headers = Some(val);
},
RPC_RATE_LIMIT_WHITELISTED_IPS => {
let ip: IpNetwork = val
.parse()
.map_err(|_| invalid_value(RPC_RATE_LIMIT_WHITELISTED_IPS, &val))?;
rate_limit_whitelisted_ips.push(ip);
},
RPC_RETRY_RANDOM_PORT => {
if retry_random_port.is_some() {
return Err(only_once_err(RPC_RETRY_RANDOM_PORT));
}
let val =
val.parse().map_err(|_| invalid_value(RPC_RETRY_RANDOM_PORT, &val))?;
retry_random_port = Some(val);
},
RPC_METHODS => {
if rpc_methods.is_some() {
return Err(only_once_err("methods"));
}
let val = val.parse().map_err(|_| invalid_value(RPC_METHODS, &val))?;
rpc_methods = Some(val);
},
RPC_OPTIONAL => {
if is_optional.is_some() {
return Err(only_once_err(RPC_OPTIONAL));
}
let val = val.parse().map_err(|_| invalid_value(RPC_OPTIONAL, &val))?;
is_optional = Some(val);
},
RPC_DISABLE_BATCH => {
if disable_batch_requests.is_some() {
return Err(only_once_err(RPC_DISABLE_BATCH));
}
let val = val.parse().map_err(|_| invalid_value(RPC_DISABLE_BATCH, &val))?;
disable_batch_requests = Some(val);
},
RPC_BATCH_LIMIT => {
if max_batch_request_len.is_some() {
return Err(only_once_err(RPC_BATCH_LIMIT));
}
let val = val.parse().map_err(|_| invalid_value(RPC_BATCH_LIMIT, &val))?;
max_batch_request_len = Some(val);
},
_ => return Err(invalid_key(key)),
}
}
let listen_addr = listen_addr.ok_or("`listen-addr` must be specified exactly once")?;
let batch_config = match (disable_batch_requests, max_batch_request_len) {
(Some(true), Some(_)) => {
return Err(format!("`{RPC_BATCH_LIMIT}` and `{RPC_DISABLE_BATCH}` are mutually exclusive and can't be used together"));
},
(Some(false), None) => RpcBatchRequestConfig::Disabled,
(None, Some(len)) => RpcBatchRequestConfig::Limit(len),
_ => RpcBatchRequestConfig::Unlimited,
};
Ok(Self {
listen_addr,
batch_config,
max_connections: max_connections.unwrap_or(RPC_DEFAULT_MAX_CONNECTIONS),
max_payload_in_mb: max_payload_in_mb.unwrap_or(RPC_DEFAULT_MAX_REQUEST_SIZE_MB),
max_payload_out_mb: max_payload_out_mb.unwrap_or(RPC_DEFAULT_MAX_RESPONSE_SIZE_MB),
cors,
max_buffer_capacity_per_connection: max_buffer_capacity_per_connection
.unwrap_or(RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN),
max_subscriptions_per_connection: max_subscriptions_per_connection
.unwrap_or(RPC_DEFAULT_MAX_SUBS_PER_CONN),
rpc_methods: rpc_methods.unwrap_or(RpcMethods::Auto),
rate_limit,
rate_limit_trust_proxy_headers: rate_limit_trust_proxy_headers.unwrap_or(false),
rate_limit_whitelisted_ips,
is_optional: is_optional.unwrap_or(false),
retry_random_port: retry_random_port.unwrap_or(false),
})
}
}
impl Into<sc_service::config::RpcEndpoint> for RpcEndpoint {
fn into(self) -> sc_service::config::RpcEndpoint {
sc_service::config::RpcEndpoint {
batch_config: self.batch_config,
listen_addr: self.listen_addr,
max_buffer_capacity_per_connection: self.max_buffer_capacity_per_connection,
max_connections: self.max_connections,
max_payload_in_mb: self.max_payload_in_mb,
max_payload_out_mb: self.max_payload_out_mb,
max_subscriptions_per_connection: self.max_subscriptions_per_connection,
rpc_methods: self.rpc_methods.into(),
rate_limit: self.rate_limit,
rate_limit_trust_proxy_headers: self.rate_limit_trust_proxy_headers,
rate_limit_whitelisted_ips: self.rate_limit_whitelisted_ips,
cors: self.cors,
retry_random_port: self.retry_random_port,
is_optional: self.is_optional,
}
}
}
impl RpcEndpoint {
/// Returns whether the endpoint is globally exposed.
pub fn is_global(&self) -> bool {
let ip = IpNetwork::from(self.listen_addr.ip());
ip.is_global()
}
}
fn only_once_err(reason: &str) -> String {
format!("`{reason}` is only allowed be specified once")
}
fn invalid_input(input: &str) -> String {
format!("`{input}`, expects: `key=value`")
}
fn invalid_value(key: &str, value: &str) -> String {
format!("value=`{value}` key=`{key}`")
}
fn invalid_key(key: &str) -> String {
format!("unknown key=`{key}`, see `--help` for available options")
}
#[cfg(test)]
mod tests {
use super::*;
use std::{num::NonZeroU32, str::FromStr};
#[test]
fn parse_rpc_endpoint_works() {
assert!(RpcEndpoint::from_str("listen-addr=127.0.0.1:9944").is_ok());
assert!(RpcEndpoint::from_str("listen-addr=[::1]:9944").is_ok());
assert!(RpcEndpoint::from_str("listen-addr=127.0.0.1:9944,methods=auto").is_ok());
assert!(RpcEndpoint::from_str("listen-addr=[::1]:9944,methods=auto").is_ok());
assert!(RpcEndpoint::from_str(
"listen-addr=127.0.0.1:9944,methods=auto,cors=*,optional=true"
)
.is_ok());
assert!(RpcEndpoint::from_str("listen-addrs=127.0.0.1:9944,foo=*").is_err());
assert!(RpcEndpoint::from_str("listen-addrs=127.0.0.1:9944,cors=").is_err());
}
#[test]
fn parse_rpc_endpoint_all() {
let endpoint = RpcEndpoint::from_str(
"listen-addr=127.0.0.1:9944,methods=unsafe,cors=*,optional=true,retry-random-port=true,rate-limit=99,\
max-batch-request-len=100,rate-limit-trust-proxy-headers=true,max-connections=33,max-request-size=4,\
max-response-size=3,max-subscriptions-per-connection=7,max-buffer-capacity-per-connection=8,\
rate-limit-whitelisted-ips=192.168.1.0/24,rate-limit-whitelisted-ips=ff01::0/32"
).unwrap();
assert_eq!(endpoint.listen_addr, ([127, 0, 0, 1], 9944).into());
assert_eq!(endpoint.rpc_methods, RpcMethods::Unsafe);
assert_eq!(endpoint.cors, Some(vec!["*".to_string()]));
assert_eq!(endpoint.is_optional, true);
assert_eq!(endpoint.retry_random_port, true);
assert_eq!(endpoint.rate_limit, Some(NonZeroU32::new(99).unwrap()));
assert!(matches!(endpoint.batch_config, RpcBatchRequestConfig::Limit(l) if l == 100));
assert_eq!(endpoint.rate_limit_trust_proxy_headers, true);
assert_eq!(
endpoint.rate_limit_whitelisted_ips,
vec![
IpNetwork::V4("192.168.1.0/24".parse().unwrap()),
IpNetwork::V6("ff01::0/32".parse().unwrap())
]
);
assert_eq!(endpoint.max_connections, 33);
assert_eq!(endpoint.max_payload_in_mb, 4);
assert_eq!(endpoint.max_payload_out_mb, 3);
assert_eq!(endpoint.max_subscriptions_per_connection, 7);
assert_eq!(endpoint.max_buffer_capacity_per_connection, 8);
}
#[test]
fn parse_rpc_endpoint_multiple_cors() {
let addr = RpcEndpoint::from_str(
"listen-addr=127.0.0.1:9944,methods=auto,cors=https://polkadot.js.org,cors=*,cors=localhost:*",
)
.unwrap();
assert_eq!(
addr.cors,
Some(vec![
"https://polkadot.js.org".to_string(),
"*".to_string(),
"localhost:*".to_string()
])
);
}
#[test]
fn parse_rpc_endpoint_whitespaces() {
let addr = RpcEndpoint::from_str(
" listen-addr = 127.0.0.1:9944, methods = auto, optional = true ",
)
.unwrap();
assert_eq!(addr.rpc_methods, RpcMethods::Auto);
assert_eq!(addr.is_optional, true);
}
#[test]
fn parse_rpc_endpoint_batch_options_mutually_exclusive() {
assert!(RpcEndpoint::from_str(
"listen-addr = 127.0.0.1:9944,disable-batch-requests=true,max-batch-request-len=100",
)
.is_err());
}
}
@@ -0,0 +1,45 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use clap::Args;
use std::str::FromStr;
/// Parameters used to config runtime.
#[derive(Debug, Clone, Args)]
pub struct RuntimeParams {
/// The size of the instances cache for each runtime [max: 32].
///
/// Values higher than 32 are illegal.
#[arg(long, default_value_t = 8, value_parser = parse_max_runtime_instances)]
pub max_runtime_instances: usize,
/// Maximum number of different runtimes that can be cached.
#[arg(long, default_value_t = 2)]
pub runtime_cache_size: u8,
}
fn parse_max_runtime_instances(s: &str) -> Result<usize, String> {
let max_runtime_instances = usize::from_str(s)
.map_err(|_err| format!("Illegal `--max-runtime-instances` value: {s}"))?;
if max_runtime_instances > 32 {
Err(format!("Illegal `--max-runtime-instances` value: {max_runtime_instances} is more than the allowed maximum of `32` "))
} else {
Ok(max_runtime_instances)
}
}
@@ -0,0 +1,148 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::arg_enums::TracingReceiver;
use clap::Args;
use sc_service::config::BasePath;
use std::path::PathBuf;
/// Shared parameters used by all `CoreParams`.
#[derive(Debug, Clone, Args)]
pub struct SharedParams {
/// Specify the chain specification.
///
/// It can be one of the predefined ones (dev, local, or staging) or it can be a path to
/// a file with the chainspec (such as one exported by the `build-spec` subcommand).
#[arg(long, value_name = "CHAIN_SPEC")]
pub chain: Option<String>,
/// Specify the development chain.
///
/// This flag sets `--chain=dev`, `--force-authoring`, `--rpc-cors=all`, `--alice`, and `--tmp`
/// flags, unless explicitly overridden. It also disables local peer discovery (see `--no-mdns`
/// and `--discover-local`). With this flag some nodes might start with manual seal, producing
/// blocks at certain events (e.g. `pezkuwi-omni-node`, which produces blocks at certain
/// intervals dictated by `--dev-block-time`).
#[arg(long)]
pub dev: bool,
/// Specify custom base path.
#[arg(long, short = 'd', value_name = "PATH")]
pub base_path: Option<PathBuf>,
/// Sets a custom logging filter (syntax: `<target>=<level>`).
///
/// Log levels (least to most verbose) are `error`, `warn`, `info`, `debug`, and `trace`.
///
/// By default, all targets log `info`. The global log level can be set with `-l<level>`.
///
/// Multiple `<target>=<level>` entries can be specified and separated by a comma.
///
/// *Example*: `--log error,sync=debug,grandpa=warn`.
/// Sets Global log level to `error`, sets `sync` target to debug and grandpa target to `warn`.
#[arg(short = 'l', long, value_name = "LOG_PATTERN", num_args = 1..)]
pub log: Vec<String>,
/// Enable detailed log output.
///
/// Includes displaying the log target, log level and thread name.
///
/// This is automatically enabled when something is logged with any higher level than `info`.
#[arg(long)]
pub detailed_log_output: bool,
/// Disable log color output.
#[arg(long)]
pub disable_log_color: bool,
/// Enable feature to dynamically update and reload the log filter.
///
/// Be aware that enabling this feature can lead to a performance decrease up to factor six or
/// more. Depending on the global logging level the performance decrease changes.
///
/// The `system_addLogFilter` and `system_resetLogFilter` RPCs will have no effect with this
/// option not being set.
#[arg(long)]
pub enable_log_reloading: bool,
/// Sets a custom profiling filter.
///
/// Syntax is the same as for logging (`--log`).
#[arg(long, value_name = "TARGETS")]
pub tracing_targets: Option<String>,
/// Receiver to process tracing messages.
#[arg(long, value_name = "RECEIVER", value_enum, ignore_case = true, default_value_t = TracingReceiver::Log)]
pub tracing_receiver: TracingReceiver,
}
impl SharedParams {
/// Specify custom base path.
pub fn base_path(&self) -> Result<Option<BasePath>, crate::Error> {
match &self.base_path {
Some(r) => Ok(Some(r.clone().into())),
// If `dev` is enabled, we use the temp base path.
None if self.is_dev() => Ok(Some(BasePath::new_temp_dir()?)),
None => Ok(None),
}
}
/// Specify the development chain.
pub fn is_dev(&self) -> bool {
self.dev
}
/// Get the chain spec for the parameters provided
pub fn chain_id(&self, is_dev: bool) -> String {
match self.chain {
Some(ref chain) => chain.clone(),
None if is_dev => "dev".into(),
_ => "".into(),
}
}
/// Get the filters for the logging
pub fn log_filters(&self) -> &[String] {
&self.log
}
/// Should the detailed log output be enabled.
pub fn detailed_log_output(&self) -> bool {
self.detailed_log_output
}
/// Should the log color output be disabled?
pub fn disable_log_color(&self) -> bool {
self.disable_log_color
}
/// Is log reloading enabled
pub fn enable_log_reloading(&self) -> bool {
self.enable_log_reloading
}
/// Receiver to process tracing messages.
pub fn tracing_receiver(&self) -> sc_service::TracingReceiver {
self.tracing_receiver.into()
}
/// Comma separated list of targets for tracing.
pub fn tracing_targets(&self) -> Option<String> {
self.tracing_targets.clone()
}
}
@@ -0,0 +1,69 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use clap::Args;
/// Parameters used to config telemetry.
#[derive(Debug, Clone, Args)]
pub struct TelemetryParams {
/// Disable connecting to the Substrate telemetry server.
///
/// Telemetry is on by default on global chains.
#[arg(long)]
pub no_telemetry: bool,
/// The URL of the telemetry server to connect to.
///
/// This flag can be passed multiple times as a means to specify multiple
/// telemetry endpoints. Verbosity levels range from 0-9, with 0 denoting
/// the least verbosity.
///
/// Expected format is 'URL VERBOSITY', e.g. `--telemetry-url 'wss://foo/bar 0'`.
#[arg(long = "telemetry-url", value_name = "URL VERBOSITY", value_parser = parse_telemetry_endpoints)]
pub telemetry_endpoints: Vec<(String, u8)>,
}
#[derive(Debug)]
enum TelemetryParsingError {
MissingVerbosity,
VerbosityParsingError(std::num::ParseIntError),
}
impl std::error::Error for TelemetryParsingError {}
impl std::fmt::Display for TelemetryParsingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TelemetryParsingError::MissingVerbosity => write!(f, "Verbosity level missing"),
TelemetryParsingError::VerbosityParsingError(e) => write!(f, "{}", e),
}
}
}
fn parse_telemetry_endpoints(s: &str) -> Result<(String, u8), TelemetryParsingError> {
let pos = s.find(' ');
match pos {
None => Err(TelemetryParsingError::MissingVerbosity),
Some(pos_) => {
let url = s[..pos_].to_string();
let verbosity =
s[pos_ + 1..].parse().map_err(TelemetryParsingError::VerbosityParsingError)?;
Ok((url, verbosity))
},
}
}
@@ -0,0 +1,75 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use clap::{Args, ValueEnum};
use sc_transaction_pool::TransactionPoolOptions;
/// Type of transaction pool to be used
#[derive(Debug, Clone, Copy, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum TransactionPoolType {
/// Uses a legacy, single-state transaction pool.
SingleState,
/// Uses a fork-aware transaction pool.
ForkAware,
}
impl Into<sc_transaction_pool::TransactionPoolType> for TransactionPoolType {
fn into(self) -> sc_transaction_pool::TransactionPoolType {
match self {
TransactionPoolType::SingleState =>
sc_transaction_pool::TransactionPoolType::SingleState,
TransactionPoolType::ForkAware => sc_transaction_pool::TransactionPoolType::ForkAware,
}
}
}
/// Parameters used to create the pool configuration.
#[derive(Debug, Clone, Args)]
pub struct TransactionPoolParams {
/// Maximum number of transactions in the transaction pool.
#[arg(long, value_name = "COUNT", default_value_t = 8192)]
pub pool_limit: usize,
/// Maximum number of kilobytes of all transactions stored in the pool.
#[arg(long, value_name = "COUNT", default_value_t = 20480)]
pub pool_kbytes: usize,
/// How long a transaction is banned for.
///
/// If it is considered invalid. Defaults to 1800s.
#[arg(long, value_name = "SECONDS")]
pub tx_ban_seconds: Option<u64>,
/// The type of transaction pool to be instantiated.
#[arg(long, value_enum, default_value_t = TransactionPoolType::ForkAware)]
pub pool_type: TransactionPoolType,
}
impl TransactionPoolParams {
/// Fill the given `PoolConfiguration` by looking at the cli parameters.
pub fn transaction_pool(&self, is_dev: bool) -> TransactionPoolOptions {
TransactionPoolOptions::new_with_params(
self.pool_limit,
self.pool_kbytes * 1024,
self.tx_ban_seconds,
self.pool_type.into(),
is_dev,
)
}
}
+420
View File
@@ -0,0 +1,420 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{error::Error as CliError, Result, Signals, SubstrateCli};
use chrono::prelude::*;
use futures::{future::FutureExt, Future};
use log::info;
use sc_service::{Configuration, Error as ServiceError, TaskManager};
use sc_utils::metrics::{TOKIO_THREADS_ALIVE, TOKIO_THREADS_TOTAL};
use std::{marker::PhantomData, time::Duration};
/// Build a tokio runtime with all features.
pub fn build_runtime() -> std::result::Result<tokio::runtime::Runtime, std::io::Error> {
tokio::runtime::Builder::new_multi_thread()
.on_thread_start(|| {
TOKIO_THREADS_ALIVE.inc();
TOKIO_THREADS_TOTAL.inc();
})
.on_thread_stop(|| {
TOKIO_THREADS_ALIVE.dec();
})
.enable_all()
.build()
}
/// A Substrate CLI runtime that can be used to run a node or a command
pub struct Runner<C: SubstrateCli> {
config: Configuration,
tokio_runtime: tokio::runtime::Runtime,
signals: Signals,
phantom: PhantomData<C>,
}
impl<C: SubstrateCli> Runner<C> {
/// Create a new runtime with the command provided in argument
pub fn new(
config: Configuration,
tokio_runtime: tokio::runtime::Runtime,
signals: Signals,
) -> Result<Runner<C>> {
Ok(Runner { config, tokio_runtime, signals, phantom: PhantomData })
}
/// Log information about the node itself.
///
/// # Example:
///
/// ```text
/// 2020-06-03 16:14:21 Substrate Node
/// 2020-06-03 16:14:21 ✌️ version 2.0.0-rc3-f4940588c-x86_64-linux-gnu
/// 2020-06-03 16:14:21 ❤️ by Parity Technologies <admin@parity.io>, 2017-2020
/// 2020-06-03 16:14:21 📋 Chain specification: Flaming Fir
/// 2020-06-03 16:14:21 🏷 Node name: jolly-rod-7462
/// 2020-06-03 16:14:21 👤 Role: FULL
/// 2020-06-03 16:14:21 💾 Database: RocksDb at /tmp/c/chains/flamingfir7/db
/// 2020-06-03 16:14:21 ⛓ Native runtime: node-251 (substrate-node-1.tx1.au10)
/// ```
fn print_node_infos(&self) {
print_node_infos::<C>(self.config())
}
/// A helper function that runs a node with tokio and stops if the process receives the signal
/// `SIGTERM` or `SIGINT`.
pub fn run_node_until_exit<F, E>(
self,
initialize: impl FnOnce(Configuration) -> F,
) -> std::result::Result<(), E>
where
F: Future<Output = std::result::Result<TaskManager, E>>,
E: std::error::Error + Send + Sync + 'static + From<ServiceError>,
{
self.print_node_infos();
let mut task_manager = self.tokio_runtime.block_on(initialize(self.config))?;
let res = self
.tokio_runtime
.block_on(self.signals.run_until_signal(task_manager.future().fuse()));
// We need to drop the task manager here to inform all tasks that they should shut down.
//
// This is important to be done before we instruct the tokio runtime to shutdown. Otherwise
// the tokio runtime will wait the full 60 seconds for all tasks to stop.
let task_registry = task_manager.into_task_registry();
// Give all futures 60 seconds to shutdown, before tokio "leaks" them.
let shutdown_timeout = Duration::from_secs(60);
self.tokio_runtime.shutdown_timeout(shutdown_timeout);
let running_tasks = task_registry.running_tasks();
if !running_tasks.is_empty() {
log::error!("Detected running(potentially stalled) tasks on shutdown:");
running_tasks.iter().for_each(|(task, count)| {
let instances_desc =
if *count > 1 { format!("with {} instances ", count) } else { "".to_string() };
if task.is_default_group() {
log::error!(
"Task \"{}\" was still running {}after waiting {} seconds to finish.",
task.name,
instances_desc,
shutdown_timeout.as_secs(),
);
} else {
log::error!(
"Task \"{}\" (Group: {}) was still running {}after waiting {} seconds to finish.",
task.name,
task.group,
instances_desc,
shutdown_timeout.as_secs(),
);
}
});
}
res.map_err(Into::into)
}
/// A helper function that runs a command with the configuration of this node.
pub fn sync_run<E>(
self,
runner: impl FnOnce(Configuration) -> std::result::Result<(), E>,
) -> std::result::Result<(), E>
where
E: std::error::Error + Send + Sync + 'static + From<ServiceError>,
{
runner(self.config)
}
/// A helper function that runs a future with tokio and stops if the process receives
/// the signal `SIGTERM` or `SIGINT`.
pub fn async_run<F, E>(
self,
runner: impl FnOnce(Configuration) -> std::result::Result<(F, TaskManager), E>,
) -> std::result::Result<(), E>
where
F: Future<Output = std::result::Result<(), E>>,
E: std::error::Error + Send + Sync + 'static + From<ServiceError> + From<CliError>,
{
let (future, task_manager) = runner(self.config)?;
self.tokio_runtime.block_on(self.signals.run_until_signal(future.fuse()))?;
// Drop the task manager before dropping the rest, to ensure that all futures were informed
// about the shut down.
drop(task_manager);
Ok(())
}
/// Get an immutable reference to the node Configuration
pub fn config(&self) -> &Configuration {
&self.config
}
/// Get a mutable reference to the node Configuration
pub fn config_mut(&mut self) -> &mut Configuration {
&mut self.config
}
}
/// Log information about the node itself.
pub fn print_node_infos<C: SubstrateCli>(config: &Configuration) {
info!("{}", C::impl_name());
info!("✌️ version {}", C::impl_version());
info!("❤️ by {}, {}-{}", C::author(), C::copyright_start_year(), Local::now().year());
info!("📋 Chain specification: {}", config.chain_spec.name());
info!("🏷 Node name: {}", config.network.node_name);
info!("👤 Role: {}", config.display_role());
info!(
"💾 Database: {} at {}",
config.database,
config
.database
.path()
.map_or_else(|| "<unknown>".to_owned(), |p| p.display().to_string())
);
}
#[cfg(test)]
mod tests {
use super::*;
use sc_network::config::NetworkConfiguration;
use sc_service::{
config::{ExecutorConfiguration, RpcConfiguration},
Arc, ChainType, GenericChainSpec, NoExtension,
};
use std::{
path::PathBuf,
sync::atomic::{AtomicU64, Ordering},
};
struct Cli;
impl SubstrateCli for Cli {
fn author() -> String {
"test".into()
}
fn impl_name() -> String {
"yep".into()
}
fn impl_version() -> String {
"version".into()
}
fn description() -> String {
"desc".into()
}
fn support_url() -> String {
"no.pe".into()
}
fn copyright_start_year() -> i32 {
2042
}
fn load_spec(
&self,
_: &str,
) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
Err("nope".into())
}
}
fn create_runner() -> Runner<Cli> {
let runtime = build_runtime().unwrap();
let root = PathBuf::from("db");
let runner = Runner::new(
Configuration {
impl_name: "spec".into(),
impl_version: "3".into(),
role: sc_service::Role::Authority,
tokio_handle: runtime.handle().clone(),
transaction_pool: Default::default(),
network: NetworkConfiguration::new_memory(),
keystore: sc_service::config::KeystoreConfig::InMemory,
database: sc_client_db::DatabaseSource::ParityDb { path: root.clone() },
trie_cache_maximum_size: None,
warm_up_trie_cache: None,
state_pruning: None,
blocks_pruning: sc_client_db::BlocksPruning::KeepAll,
chain_spec: Box::new(
GenericChainSpec::<NoExtension, ()>::builder(
Default::default(),
NoExtension::None,
)
.with_name("test")
.with_id("test_id")
.with_chain_type(ChainType::Development)
.with_genesis_config_patch(Default::default())
.build(),
),
executor: ExecutorConfiguration::default(),
wasm_runtime_overrides: None,
rpc: RpcConfiguration {
addr: None,
max_connections: Default::default(),
cors: None,
methods: Default::default(),
max_request_size: Default::default(),
max_response_size: Default::default(),
id_provider: Default::default(),
max_subs_per_conn: Default::default(),
message_buffer_capacity: Default::default(),
port: 9944,
batch_config: sc_service::config::RpcBatchRequestConfig::Unlimited,
rate_limit: None,
rate_limit_whitelisted_ips: Default::default(),
rate_limit_trust_proxy_headers: Default::default(),
request_logger_limit: 1024,
},
prometheus_config: None,
telemetry_endpoints: None,
offchain_worker: Default::default(),
force_authoring: false,
disable_grandpa: false,
dev_key_seed: None,
tracing_targets: None,
tracing_receiver: Default::default(),
announce_block: true,
base_path: sc_service::BasePath::new(root.clone()),
data_path: root,
},
runtime,
Signals::dummy(),
)
.unwrap();
runner
}
#[test]
fn ensure_run_until_exit_informs_tasks_to_end() {
let runner = create_runner();
let counter = Arc::new(AtomicU64::new(0));
let counter2 = counter.clone();
runner
.run_node_until_exit(move |cfg| async move {
let task_manager = TaskManager::new(cfg.tokio_handle.clone(), None).unwrap();
let (sender, receiver) = futures::channel::oneshot::channel();
// We need to use `spawn_blocking` here so that we get a dedicated thread for our
// future. This is important for this test, as otherwise tokio can just "drop" the
// future.
task_manager.spawn_handle().spawn_blocking("test", None, async move {
let _ = sender.send(());
loop {
counter2.fetch_add(1, Ordering::Relaxed);
futures_timer::Delay::new(Duration::from_millis(50)).await;
}
});
task_manager.spawn_essential_handle().spawn_blocking("test2", None, async {
// Let's stop this essential task directly when our other task started.
// It will signal that the task manager should end.
let _ = receiver.await;
});
Ok::<_, sc_service::Error>(task_manager)
})
.unwrap_err();
let count = counter.load(Ordering::Relaxed);
// Ensure that our counting task was running for less than 30 seconds.
// It should be directly killed, but for CI and whatever we are being a little bit more
// "relaxed".
assert!((count as u128) < (Duration::from_secs(30).as_millis() / 50));
}
fn run_test_in_another_process(
test_name: &str,
test_body: impl FnOnce(),
) -> Option<std::process::Output> {
if std::env::var("RUN_FORKED_TEST").is_ok() {
test_body();
None
} else {
let output = std::process::Command::new(std::env::current_exe().unwrap())
.arg(test_name)
.env("RUN_FORKED_TEST", "1")
.output()
.unwrap();
assert!(output.status.success());
Some(output)
}
}
/// This test ensures that `run_node_until_exit` aborts waiting for "stuck" tasks after 60
/// seconds, aka doesn't wait until they are finished (which may never happen).
#[test]
fn ensure_run_until_exit_is_not_blocking_indefinitely() {
let output = run_test_in_another_process(
"ensure_run_until_exit_is_not_blocking_indefinitely",
|| {
sp_tracing::try_init_simple();
let runner = create_runner();
runner
.run_node_until_exit(move |cfg| async move {
let task_manager =
TaskManager::new(cfg.tokio_handle.clone(), None).unwrap();
let (sender, receiver) = futures::channel::oneshot::channel();
// We need to use `spawn_blocking` here so that we get a dedicated thread
// for our future. This future is more blocking code that will never end.
task_manager.spawn_handle().spawn_blocking("test", None, async move {
let _ = sender.send(());
loop {
std::thread::sleep(Duration::from_secs(30));
}
});
task_manager.spawn_essential_handle().spawn_blocking(
"test2",
None,
async {
// Let's stop this essential task directly when our other task
// started. It will signal that the task manager should end.
let _ = receiver.await;
},
);
Ok::<_, sc_service::Error>(task_manager)
})
.unwrap_err();
},
);
let Some(output) = output else { return };
let stderr = dbg!(String::from_utf8(output.stderr).unwrap());
assert!(
stderr.contains("Task \"test\" was still running after waiting 60 seconds to finish.")
);
assert!(!stderr
.contains("Task \"test2\" was still running after waiting 60 seconds to finish."));
}
}
+107
View File
@@ -0,0 +1,107 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use futures::{
future::{self, BoxFuture, FutureExt},
pin_mut, select, Future,
};
use sc_service::Error as ServiceError;
/// Abstraction over OS signals to handle the shutdown of the node smoothly.
///
/// On `unix` this represents `SigInt` and `SigTerm`.
pub struct Signals(BoxFuture<'static, ()>);
impl Signals {
/// Return the signals future.
pub fn future(self) -> BoxFuture<'static, ()> {
self.0
}
/// Capture the relevant signals to handle shutdown of the node smoothly.
///
/// Needs to be called in a Tokio context to have access to the tokio reactor.
#[cfg(target_family = "unix")]
pub fn capture() -> std::result::Result<Self, ServiceError> {
use tokio::signal::unix::{signal, SignalKind};
let mut stream_int = signal(SignalKind::interrupt()).map_err(ServiceError::Io)?;
let mut stream_term = signal(SignalKind::terminate()).map_err(ServiceError::Io)?;
Ok(Signals(
async move {
future::select(stream_int.recv().boxed(), stream_term.recv().boxed()).await;
}
.boxed(),
))
}
/// Capture the relevant signals to handle shutdown of the node smoothly.
///
/// Needs to be called in a Tokio context to have access to the tokio reactor.
#[cfg(not(unix))]
pub fn capture() -> Result<Self, ServiceError> {
use tokio::signal::ctrl_c;
Ok(Signals(
async move {
let _ = ctrl_c().await;
}
.boxed(),
))
}
/// A dummy signal that never returns.
pub fn dummy() -> Self {
Self(future::pending().boxed())
}
/// Run a future task until receive a signal.
pub async fn run_until_signal<F, E>(self, func: F) -> Result<(), E>
where
F: Future<Output = Result<(), E>> + future::FusedFuture,
E: std::error::Error + Send + Sync + 'static,
{
let signals = self.future().fuse();
pin_mut!(func, signals);
select! {
_ = signals => {},
res = func => res?,
}
Ok(())
}
/// Execute the future task and returns it's value if it completes before the signal.
pub async fn try_until_signal<F, T>(self, func: F) -> Result<T, ()>
where
F: Future<Output = T> + future::FusedFuture,
{
let signals = self.future().fuse();
pin_mut!(func, signals);
select! {
s = signals => Err(s),
res = func => Ok(res),
}
}
}