feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -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",
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
Substrate CLI library.
|
||||
|
||||
License: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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),*)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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."));
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user