Newer
Older
Cecile Tonglet
committed
// This file is part of Substrate.
// Copyright (C) 2018-2020 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
Cecile Tonglet
committed
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
Cecile Tonglet
committed
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
Cecile Tonglet
committed
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Cecile Tonglet
committed
// 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::RpcMethods;
Cecile Tonglet
committed
use crate::error::{Error, Result};
use crate::params::ImportParams;
use crate::params::KeystoreParams;
use crate::params::NetworkParams;
Cecile Tonglet
committed
use crate::params::OffchainWorkerParams;
Cecile Tonglet
committed
use crate::params::SharedParams;
use crate::params::TransactionPoolParams;
use crate::CliConfiguration;
Cecile Tonglet
committed
use regex::Regex;
use sc_service::{
Cecile Tonglet
committed
config::{BasePath, MultiaddrWithPeerId, PrometheusConfig, TransactionPoolOptions},
Cecile Tonglet
committed
ChainSpec, Role,
Cecile Tonglet
committed
};
use sc_telemetry::TelemetryEndpoints;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
Cecile Tonglet
committed
/// The `run` command used to run a node.
Cecile Tonglet
committed
#[derive(Debug, StructOpt)]
Cecile Tonglet
committed
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).
#[structopt(
long = "validator",
conflicts_with_all = &[ "sentry" ]
)]
pub validator: bool,
/// Enable sentry mode.
///
/// The node will be started with the authority role and participate in
/// consensus tasks as an "observer", it will never actively participate
/// regardless of whether it could (e.g. keys are available locally). This
/// mode is useful as a secure proxy for validators (which would run
/// detached from the network), since we want this node to participate in
/// the full consensus protocols in order to have all needed consensus data
/// available to relay to private nodes.
#[structopt(
long = "sentry",
conflicts_with_all = &[ "validator", "light" ],
parse(try_from_str)
Cecile Tonglet
committed
)]
pub sentry: Vec<MultiaddrWithPeerId>,
Cecile Tonglet
committed
/// Disable GRANDPA voter when running in validator mode, otherwise disable the GRANDPA observer.
Cecile Tonglet
committed
pub no_grandpa: bool,
/// Experimental: Run in light client mode.
#[structopt(long = "light", conflicts_with = "sentry")]
pub light: bool,
/// Listen to all RPC interfaces.
///
/// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC proxy
Cecile Tonglet
committed
/// server to filter out dangerous methods. More details: https://github.com/paritytech/substrate/wiki/Public-RPC.
/// Use `--unsafe-rpc-external` to suppress the warning if you understand the risks.
#[structopt(long = "rpc-external")]
pub rpc_external: bool,
/// Listen to all RPC interfaces.
///
/// Same as `--rpc-external`.
#[structopt(long)]
Cecile Tonglet
committed
pub unsafe_rpc_external: bool,
/// RPC methods to expose.
/// - `Unsafe`: Exposes every RPC method.
/// - `Safe`: Exposes only a safe subset of RPC methods, denying unsafe RPC methods.
/// - `Auto`: Acts as `Safe` if RPC is served externally, e.g. when `--{rpc,ws}-external` is passed,
/// otherwise acts as `Unsafe`.
#[structopt(
long,
value_name = "METHOD SET",
possible_values = &RpcMethods::variants(),
case_insensitive = true,
default_value = "Auto",
verbatim_doc_comment
)]
pub rpc_methods: RpcMethods,
Cecile Tonglet
committed
/// Listen to all Websocket interfaces.
///
/// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC proxy
Cecile Tonglet
committed
/// server to filter out dangerous methods. More details: https://github.com/paritytech/substrate/wiki/Public-RPC.
/// Use `--unsafe-ws-external` to suppress the warning if you understand the risks.
#[structopt(long = "ws-external")]
pub ws_external: bool,
/// Listen to all Websocket interfaces.
///
/// Same as `--ws-external` but doesn't warn you about it.
#[structopt(long = "unsafe-ws-external")]
pub unsafe_ws_external: bool,
/// Listen to all Prometheus data source interfaces.
///
/// Default is local.
#[structopt(long = "prometheus-external")]
pub prometheus_external: bool,
/// Specify IPC RPC server path
#[structopt(long = "ipc-path", value_name = "PATH")]
pub ipc_path: Option<String>,
Cecile Tonglet
committed
/// Specify HTTP RPC server TCP port.
#[structopt(long = "rpc-port", value_name = "PORT")]
pub rpc_port: Option<u16>,
/// Specify WebSockets RPC server TCP port.
#[structopt(long = "ws-port", value_name = "PORT")]
pub ws_port: Option<u16>,
/// Maximum number of WS RPC server connections.
#[structopt(long = "ws-max-connections", value_name = "COUNT")]
pub ws_max_connections: Option<usize>,
/// 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.
Cecile Tonglet
committed
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#[structopt(long = "rpc-cors", value_name = "ORIGINS", parse(try_from_str = parse_cors))]
pub rpc_cors: Option<Cors>,
/// Specify Prometheus data source server TCP Port.
#[structopt(long = "prometheus-port", value_name = "PORT")]
pub prometheus_port: Option<u16>,
/// Do not expose a Prometheus metric endpoint.
///
/// Prometheus metric endpoint is enabled by default.
#[structopt(long = "no-prometheus")]
pub no_prometheus: bool,
/// The human-readable name for this node.
///
/// The node name will be reported to the telemetry server, if enabled.
#[structopt(long = "name", value_name = "NAME")]
pub name: Option<String>,
/// Disable connecting to the Substrate telemetry server.
///
/// Telemetry is on by default on global chains.
#[structopt(long = "no-telemetry")]
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
Cecile Tonglet
committed
/// 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'`.
Cecile Tonglet
committed
#[structopt(long = "telemetry-url", value_name = "URL VERBOSITY", parse(try_from_str = parse_telemetry_endpoints))]
pub telemetry_endpoints: Vec<(String, u8)>,
#[allow(missing_docs)]
#[structopt(flatten)]
pub offchain_worker_params: OffchainWorkerParams,
Cecile Tonglet
committed
#[allow(missing_docs)]
#[structopt(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[structopt(flatten)]
pub import_params: ImportParams,
#[allow(missing_docs)]
#[structopt(flatten)]
Cecile Tonglet
committed
pub network_params: NetworkParams,
Cecile Tonglet
committed
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#[allow(missing_docs)]
#[structopt(flatten)]
pub pool_config: TransactionPoolParams,
/// Shortcut for `--name Alice --validator` with session keys for `Alice` added to keystore.
#[structopt(long, conflicts_with_all = &["bob", "charlie", "dave", "eve", "ferdie", "one", "two"])]
pub alice: bool,
/// Shortcut for `--name Bob --validator` with session keys for `Bob` added to keystore.
#[structopt(long, conflicts_with_all = &["alice", "charlie", "dave", "eve", "ferdie", "one", "two"])]
pub bob: bool,
/// Shortcut for `--name Charlie --validator` with session keys for `Charlie` added to keystore.
#[structopt(long, conflicts_with_all = &["alice", "bob", "dave", "eve", "ferdie", "one", "two"])]
pub charlie: bool,
/// Shortcut for `--name Dave --validator` with session keys for `Dave` added to keystore.
#[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "eve", "ferdie", "one", "two"])]
pub dave: bool,
/// Shortcut for `--name Eve --validator` with session keys for `Eve` added to keystore.
#[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "ferdie", "one", "two"])]
pub eve: bool,
/// Shortcut for `--name Ferdie --validator` with session keys for `Ferdie` added to keystore.
#[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "one", "two"])]
pub ferdie: bool,
/// Shortcut for `--name One --validator` with session keys for `One` added to keystore.
#[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "two"])]
pub one: bool,
/// Shortcut for `--name Two --validator` with session keys for `Two` added to keystore.
#[structopt(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "one"])]
pub two: bool,
/// Enable authoring even when offline.
#[structopt(long = "force-authoring")]
pub force_authoring: bool,
Cecile Tonglet
committed
#[allow(missing_docs)]
#[structopt(flatten)]
pub keystore_params: KeystoreParams,
/// The size of the instances cache for each runtime.
///
/// The default value is 8 and the values higher than 256 are ignored.
Cecile Tonglet
committed
#[structopt(long)]
pub max_runtime_instances: Option<usize>,
/// Specify a list of sentry node public addresses.
///
/// Can't be used with --public-addr as the sentry node would take precedence over the public address
/// specified there.
Cecile Tonglet
committed
#[structopt(
long = "sentry-nodes",
value_name = "ADDR",
conflicts_with_all = &[ "sentry", "public-addr" ]
Cecile Tonglet
committed
)]
pub sentry_nodes: Vec<MultiaddrWithPeerId>,
Cecile Tonglet
committed
/// 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.
#[structopt(long, conflicts_with = "base-path")]
pub tmp: bool,
Cecile Tonglet
committed
}
impl RunCmd {
/// Get the `Sr25519Keyring` matching one of the flag.
Cecile Tonglet
committed
pub fn get_keyring(&self) -> Option<sp_keyring::Sr25519Keyring> {
use sp_keyring::Sr25519Keyring::*;
Cecile Tonglet
committed
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)
Cecile Tonglet
committed
} else {
None
Cecile Tonglet
committed
}
}
}
Cecile Tonglet
committed
Cecile Tonglet
committed
impl CliConfiguration for RunCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
Cecile Tonglet
committed
Cecile Tonglet
committed
fn import_params(&self) -> Option<&ImportParams> {
Some(&self.import_params)
}
Cecile Tonglet
committed
Cecile Tonglet
committed
fn network_params(&self) -> Option<&NetworkParams> {
Some(&self.network_params)
}
Cecile Tonglet
committed
Cecile Tonglet
committed
fn keystore_params(&self) -> Option<&KeystoreParams> {
Some(&self.keystore_params)
}
Cecile Tonglet
committed
fn offchain_worker_params(&self) -> Option<&OffchainWorkerParams> {
Some(&self.offchain_worker_params)
}
Cecile Tonglet
committed
fn node_name(&self) -> Result<String> {
let name: String = match (self.name.as_ref(), self.get_keyring()) {
Cecile Tonglet
committed
(Some(name), _) => name.to_string(),
(_, Some(keyring)) => keyring.to_string(),
Cecile Tonglet
committed
(None, None) => crate::generate_node_name(),
Cecile Tonglet
committed
};
Cecile Tonglet
committed
is_node_name_valid(&name).map_err(|msg| {
Error::Input(format!(
"Invalid node name '{}'. Reason: {}. If unsure, use none.",
name, msg
Cecile Tonglet
committed
})?;
Cecile Tonglet
committed
Cecile Tonglet
committed
Ok(name)
}
Cecile Tonglet
committed
Cecile Tonglet
committed
fn dev_key_seed(&self, is_dev: bool) -> Result<Option<String>> {
Ok(self.get_keyring().map(|a| format!("//{}", a)).or_else(|| {
if is_dev && !self.light {
Some("//Alice".into())
} else {
None
}
}))
}
Cecile Tonglet
committed
Cecile Tonglet
committed
fn telemetry_endpoints(
&self,
chain_spec: &Box<dyn ChainSpec>,
) -> Result<Option<TelemetryEndpoints>> {
Ok(if self.no_telemetry {
None
} else if !self.telemetry_endpoints.is_empty() {
Some(
TelemetryEndpoints::new(self.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_light = self.light;
let is_authority = (self.validator || is_dev || keyring.is_some()) && !is_light;
Cecile Tonglet
committed
Cecile Tonglet
committed
Ok(if is_light {
sc_service::Role::Light
} else if is_authority {
sc_service::Role::Authority {
sentry_nodes: self.sentry_nodes.clone(),
}
} else if !self.sentry.is_empty() {
sc_service::Role::Sentry {
validators: self.sentry.clone(),
}
Cecile Tonglet
committed
} else {
Cecile Tonglet
committed
sc_service::Role::Full
})
}
fn force_authoring(&self) -> Result<bool> {
// Imply forced authoring on --dev
Ok(self.shared_params.dev || self.force_authoring)
}
Cecile Tonglet
committed
Cecile Tonglet
committed
fn prometheus_config(&self) -> Result<Option<PrometheusConfig>> {
Ok(if self.no_prometheus {
None
Cecile Tonglet
committed
} else {
let interface = if self.prometheus_external {
Ipv4Addr::UNSPECIFIED
Cecile Tonglet
committed
} else {
Cecile Tonglet
committed
};
Some(PrometheusConfig::new_with_default_registry(
SocketAddr::new(interface.into(), self.prometheus_port.unwrap_or(9615))
))
})
Cecile Tonglet
committed
}
Cecile Tonglet
committed
Cecile Tonglet
committed
fn disable_grandpa(&self) -> Result<bool> {
Ok(self.no_grandpa)
}
Cecile Tonglet
committed
Cecile Tonglet
committed
fn rpc_ws_max_connections(&self) -> Result<Option<usize>> {
Ok(self.ws_max_connections)
Cecile Tonglet
committed
}
Cecile Tonglet
committed
fn rpc_cors(&self, is_dev: bool) -> 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())
}
fn rpc_http(&self) -> Result<Option<SocketAddr>> {
let interface = rpc_interface(
self.rpc_external,
self.unsafe_rpc_external,
self.rpc_methods,
self.validator
)?;
Ok(Some(SocketAddr::new(interface, self.rpc_port.unwrap_or(9933))))
Cecile Tonglet
committed
}
fn rpc_ipc(&self) -> Result<Option<String>> {
Ok(self.ipc_path.clone())
}
Cecile Tonglet
committed
fn rpc_ws(&self) -> Result<Option<SocketAddr>> {
let interface = rpc_interface(
self.ws_external,
self.unsafe_ws_external,
self.rpc_methods,
self.validator
)?;
Ok(Some(SocketAddr::new(interface, self.ws_port.unwrap_or(9944))))
}
Cecile Tonglet
committed
fn rpc_methods(&self) -> Result<sc_service::config::RpcMethods> {
Ok(self.rpc_methods.into())
Cecile Tonglet
committed
}
fn transaction_pool(&self) -> Result<TransactionPoolOptions> {
Ok(self.pool_config.transaction_pool())
}
fn max_runtime_instances(&self) -> Result<Option<usize>> {
Ok(self.max_runtime_instances.map(|x| x.min(256)))
Cecile Tonglet
committed
}
Cecile Tonglet
committed
fn base_path(&self) -> Result<Option<BasePath>> {
Ok(if self.tmp {
Some(BasePath::new_temp_dir()?)
} else {
self.shared_params().base_path()
})
}
Cecile Tonglet
committed
}
/// Check whether a node name is considered as valid.
Cecile Tonglet
committed
pub fn is_node_name_valid(_name: &str) -> std::result::Result<(), &str> {
Cecile Tonglet
committed
let name = _name.to_string();
Cecile Tonglet
committed
if name.chars().count() >= crate::NODE_NAME_MAX_LENGTH {
Cecile Tonglet
committed
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?:\\/+)?(www)+";
let re = Regex::new(invalid_patterns).unwrap();
if re.is_match(&name) {
return Err("Node name should not contain urls");
}
Ok(())
}
Cecile Tonglet
committed
is_external: bool,
is_unsafe_external: bool,
rpc_methods: RpcMethods,
Cecile Tonglet
committed
is_validator: bool,
) -> Result<IpAddr> {
if is_external && is_validator && rpc_methods != RpcMethods::Unsafe {
Cecile Tonglet
committed
return Err(Error::Input(
"--rpc-external and --ws-external options 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."
Cecile Tonglet
committed
.to_owned(),
));
Cecile Tonglet
committed
}
if is_external || is_unsafe_external {
if rpc_methods == RpcMethods::Unsafe {
log::warn!(
"It isn't safe to expose RPC publicly without a proxy server that filters \
available set of RPC methods."
);
}
Cecile Tonglet
committed
Ok(Ipv4Addr::UNSPECIFIED.into())
Cecile Tonglet
committed
} else {
Ok(Ipv4Addr::LOCALHOST.into())
Cecile Tonglet
committed
}
}
#[derive(Debug)]
enum TelemetryParsingError {
MissingVerbosity,
VerbosityParsingError(std::num::ParseIntError),
}
impl std::error::Error for TelemetryParsingError {}
Cecile Tonglet
committed
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),
}
}
}
Cecile Tonglet
committed
fn parse_telemetry_endpoints(s: &str) -> std::result::Result<(String, u8), TelemetryParsingError> {
Cecile Tonglet
committed
let pos = s.find(' ');
match pos {
None => Err(TelemetryParsingError::MissingVerbosity),
Cecile Tonglet
committed
Some(pos_) => {
let url = s[..pos_].to_string();
Cecile Tonglet
committed
let verbosity = s[pos_ + 1..]
.parse()
.map_err(TelemetryParsingError::VerbosityParsingError)?;
Cecile Tonglet
committed
Ok((url, verbosity))
}
}
}
/// CORS setting
///
/// The type is introduced to overcome `Option<Option<T>>`
/// handling of `structopt`.
#[derive(Clone, Debug)]
pub enum Cors {
Cecile Tonglet
committed
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),
}
}
}
Cecile Tonglet
committed
fn parse_cors(s: &str) -> std::result::Result<Cors, Box<dyn std::error::Error>> {
Cecile Tonglet
committed
let mut is_all = false;
let mut origins = Vec::new();
for part in s.split(',') {
match part {
"all" | "*" => {
is_all = true;
break;
Cecile Tonglet
committed
}
Cecile Tonglet
committed
other => origins.push(other.to_owned()),
}
}
Cecile Tonglet
committed
Ok(if is_all {
Cors::All
} else {
Cors::List(origins)
})
Cecile Tonglet
committed
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tests_node_name_good() {
assert!(is_node_name_valid("short name").is_ok());
}
#[test]
fn tests_node_name_bad() {
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());
Cecile Tonglet
committed
assert!(is_node_name_valid("Dots.not.Ok").is_err());
assert!(is_node_name_valid("http://visit.me").is_err());
assert!(is_node_name_valid("https://visit.me").is_err());
assert!(is_node_name_valid("www.visit.me").is_err());
assert!(is_node_name_valid("email@domain").is_err());
}
}