) -> Result,
B: ServiceBuilderRevert,
C: Default,
G: RuntimeGenesis,
E: ChainSpecExtension,
{
let config = create_config_with_db_path(
spec_factory, &self.params.shared_params, self.version
)?;
let blocks = self.params.num;
builder(config)?.revert_chain(blocks.into())?;
Ok(())
}
}
/// Create a `NodeKeyConfig` from the given `NodeKeyParams` in the context
/// of an optional network config storage directory.
fn node_key_config (params: NodeKeyParams, net_config_dir: &Option
)
-> error::Result
where
P: AsRef
{
match params.node_key_type {
NodeKeyType::Ed25519 =>
params.node_key.as_ref().map(parse_ed25519_secret).unwrap_or_else(||
Ok(params.node_key_file
.or_else(|| net_config_file(net_config_dir, NODE_KEY_ED25519_FILE))
.map(network::config::Secret::File)
.unwrap_or(network::config::Secret::New)))
.map(NodeKeyConfig::Ed25519)
}
}
fn net_config_file(net_config_dir: &Option
, name: &str) -> Option
where
P: AsRef
{
net_config_dir.as_ref().map(|d| d.as_ref().join(name))
}
/// 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 `network::Secret`.
fn parse_ed25519_secret(hex: &String) -> error::Result {
H256::from_str(&hex).map_err(invalid_node_key).and_then(|bytes|
network::config::identity::ed25519::SecretKey::from_bytes(bytes)
.map(network::config::Secret::Input)
.map_err(invalid_node_key))
}
/// Fill the given `PoolConfiguration` by looking at the cli parameters.
fn fill_transaction_pool_configuration(
options: &mut Configuration,
params: TransactionPoolParams,
) -> error::Result<()> {
// ready queue
options.transaction_pool.ready.count = params.pool_limit;
options.transaction_pool.ready.total_bytes = params.pool_kbytes * 1024;
// future queue
let factor = 10;
options.transaction_pool.future.count = params.pool_limit / factor;
options.transaction_pool.future.total_bytes = params.pool_kbytes * 1024 / factor;
Ok(())
}
/// Fill the given `NetworkConfiguration` by looking at the cli parameters.
fn fill_network_configuration(
cli: NetworkConfigurationParams,
base_path: &Path,
chain_spec_id: &str,
config: &mut NetworkConfiguration,
client_id: String,
is_dev: bool,
) -> error::Result<()> {
config.boot_nodes.extend(cli.bootnodes.into_iter());
config.config_path = Some(
network_path(&base_path, chain_spec_id).to_string_lossy().into()
);
config.net_config_path = config.config_path.clone();
config.reserved_nodes.extend(cli.reserved_nodes.into_iter());
if cli.reserved_only {
config.non_reserved_mode = NonReservedPeerMode::Deny;
}
for addr in cli.listen_addr.iter() {
let addr = addr.parse().ok().ok_or(error::Error::InvalidListenMultiaddress)?;
config.listen_addresses.push(addr);
}
if config.listen_addresses.is_empty() {
let port = match cli.port {
Some(port) => port,
None => 30333,
};
config.listen_addresses = vec![
iter::once(Protocol::Ip4(Ipv4Addr::new(0, 0, 0, 0)))
.chain(iter::once(Protocol::Tcp(port)))
.collect()
];
}
config.public_addresses = Vec::new();
config.client_version = client_id;
config.node_key = node_key_config(cli.node_key_params, &config.net_config_path)?;
config.in_peers = cli.in_peers;
config.out_peers = cli.out_peers;
config.transport = TransportConfig::Normal {
enable_mdns: !is_dev && !cli.no_mdns,
wasm_external_transport: None,
};
Ok(())
}
fn input_keystore_password() -> Result {
rpassword::read_password_from_tty(Some("Keystore password: "))
.map_err(|e| format!("{:?}", e))
}
/// Fill the password field of the given config instance.
fn fill_config_keystore_password(
config: &mut service::Configuration,
cli: &RunCmd,
) -> Result<(), String> {
config.keystore_password = if cli.password_interactive {
Some(input_keystore_password()?.into())
} else if let Some(ref file) = cli.password_filename {
Some(fs::read_to_string(file).map_err(|e| format!("{}", e))?.into())
} else if let Some(ref password) = cli.password {
Some(password.clone().into())
} else {
None
};
Ok(())
}
fn create_run_node_config(
cli: RunCmd, spec_factory: S, impl_name: &'static str, version: &VersionInfo,
) -> error::Result>
where
C: Default,
G: RuntimeGenesis,
E: ChainSpecExtension,
S: FnOnce(&str) -> Result>, String>,
{
let spec = load_spec(&cli.shared_params, spec_factory)?;
let mut config = service::Configuration::default_with_spec(spec.clone());
fill_config_keystore_password(&mut config, &cli)?;
config.impl_name = impl_name;
config.impl_commit = version.commit;
config.impl_version = version.version;
config.name = match cli.name.or(cli.keyring.account.map(|a| a.to_string())) {
None => generate_node_name(),
Some(name) => name,
};
match is_node_name_valid(&config.name) {
Ok(_) => (),
Err(msg) => Err(
error::Error::Input(
format!("Invalid node name '{}'. Reason: {}. If unsure, use none.",
config.name,
msg
)
)
)?
}
let base_path = base_path(&cli.shared_params, version);
config.keystore_path = cli.keystore_path.unwrap_or_else(
|| keystore_path(&base_path, config.chain_spec.id())
);
config.database_path = db_path(&base_path, config.chain_spec.id());
config.database_cache_size = cli.database_cache_size;
config.state_cache_size = cli.state_cache_size;
let is_dev = cli.shared_params.dev;
let role =
if cli.light {
service::Roles::LIGHT
} else if cli.validator || is_dev || cli.keyring.account.is_some() {
service::Roles::AUTHORITY
} else {
service::Roles::FULL
};
// by default we disable pruning if the node is an authority (i.e.
// `ArchiveAll`), otherwise we keep state for the last 256 blocks. if the
// node is an authority and pruning is enabled explicitly, then we error
// unless `unsafe_pruning` is set.
config.pruning = match cli.pruning {
Some(ref s) if s == "archive" => PruningMode::ArchiveAll,
None if role == service::Roles::AUTHORITY => PruningMode::ArchiveAll,
None => PruningMode::default(),
Some(s) => {
if role == service::Roles::AUTHORITY && !cli.unsafe_pruning {
return Err(error::Error::Input(
"Validators should run with state pruning disabled (i.e. archive). \
You can ignore this check with `--unsafe-pruning`.".to_string()
));
}
PruningMode::keep_blocks(s.parse()
.map_err(|_| error::Error::Input("Invalid pruning mode specified".to_string()))?
)
},
};
config.wasm_method = cli.wasm_method.into();
let exec = cli.execution_strategies;
let exec_all_or = |strat: params::ExecutionStrategy| exec.execution.unwrap_or(strat).into();
config.execution_strategies = ExecutionStrategies {
syncing: exec_all_or(exec.execution_syncing),
importing: exec_all_or(exec.execution_import_block),
block_construction: exec_all_or(exec.execution_block_construction),
offchain_worker: exec_all_or(exec.execution_offchain_worker),
other: exec_all_or(exec.execution_other),
};
config.offchain_worker = match (cli.offchain_worker, role) {
(params::OffchainWorkerEnabled::WhenValidating, service::Roles::AUTHORITY) => true,
(params::OffchainWorkerEnabled::Always, _) => true,
(params::OffchainWorkerEnabled::Never, _) => false,
(params::OffchainWorkerEnabled::WhenValidating, _) => false,
};
config.roles = role;
config.disable_grandpa = cli.no_grandpa;
let client_id = config.client_id();
fill_network_configuration(
cli.network_config,
&base_path,
spec.id(),
&mut config.network,
client_id,
is_dev,
)?;
fill_transaction_pool_configuration(&mut config, cli.pool_config)?;
config.dev_key_seed = cli.keyring.account
.map(|a| format!("//{}", a)).or_else(|| {
if is_dev {
Some("//Alice".into())
} else {
None
}
});
let rpc_interface: &str = if cli.rpc_external { "0.0.0.0" } else { "127.0.0.1" };
let ws_interface: &str = if cli.ws_external { "0.0.0.0" } else { "127.0.0.1" };
config.rpc_http = Some(parse_address(&format!("{}:{}", rpc_interface, 9933), cli.rpc_port)?);
config.rpc_ws = Some(parse_address(&format!("{}:{}", ws_interface, 9944), cli.ws_port)?);
config.rpc_ws_max_connections = cli.ws_max_connections;
config.rpc_cors = cli.rpc_cors.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(),
"https://substrate-ui.parity.io".into(),
])
}).into();
// Override telemetry
if cli.no_telemetry {
config.telemetry_endpoints = None;
} else if !cli.telemetry_endpoints.is_empty() {
config.telemetry_endpoints = Some(TelemetryEndpoints::new(cli.telemetry_endpoints));
}
// Imply forced authoring on --dev
config.force_authoring = cli.shared_params.dev || cli.force_authoring;
Ok(config)
}
//
// IANA unassigned port ranges that we could use:
// 6717-6766 Unassigned
// 8504-8553 Unassigned
// 9556-9591 Unassigned
// 9803-9874 Unassigned
// 9926-9949 Unassigned
fn with_default_boot_node(
spec: &mut ChainSpec,
cli: BuildSpecCmd,
version: &VersionInfo,
) -> error::Result<()>
where
G: RuntimeGenesis,
E: ChainSpecExtension,
{
if spec.boot_nodes().is_empty() && !cli.disable_default_bootnode {
let base_path = base_path(&cli.shared_params, version);
let storage_path = network_path(&base_path, spec.id());
let node_key = node_key_config(cli.node_key_params, &Some(storage_path))?;
let keys = node_key.into_keypair()?;
let peer_id = keys.public().into_peer_id();
let addr = build_multiaddr![
Ip4([127, 0, 0, 1]),
Tcp(30333u16),
P2p(peer_id)
];
spec.add_boot_node(addr)
}
Ok(())
}
/// Creates a configuration including the database path.
pub fn create_config_with_db_path(
spec_factory: S, cli: &SharedParams, version: &VersionInfo,
) -> error::Result>
where
C: Default,
G: RuntimeGenesis,
E: ChainSpecExtension,
S: FnOnce(&str) -> Result>, String>,
{
let spec = load_spec(cli, spec_factory)?;
let base_path = base_path(cli, version);
let mut config = service::Configuration::default_with_spec(spec.clone());
config.database_path = db_path(&base_path, spec.id());
Ok(config)
}
/// Internal trait used to cast to a dynamic type that implements Read and Seek.
trait ReadPlusSeek: Read + Seek {}
impl ReadPlusSeek for T {}
fn parse_address(
address: &str,
port: Option,
) -> Result {
let mut address: SocketAddr = address.parse().map_err(
|_| format!("Invalid address: {}", address)
)?;
if let Some(port) = port {
address.set_port(port);
}
Ok(address)
}
fn keystore_path(base_path: &Path, chain_id: &str) -> PathBuf {
let mut path = base_path.to_owned();
path.push("chains");
path.push(chain_id);
path.push("keystore");
path
}
fn db_path(base_path: &Path, chain_id: &str) -> PathBuf {
let mut path = base_path.to_owned();
path.push("chains");
path.push(chain_id);
path.push("db");
path
}
fn network_path(base_path: &Path, chain_id: &str) -> PathBuf {
let mut path = base_path.to_owned();
path.push("chains");
path.push(chain_id);
path.push("network");
path
}
fn init_logger(pattern: &str) {
use ansi_term::Colour;
let mut builder = env_logger::Builder::new();
// Disable info logging by default for some modules:
builder.filter(Some("ws"), log::LevelFilter::Off);
builder.filter(Some("hyper"), log::LevelFilter::Warn);
// Enable info for others.
builder.filter(None, log::LevelFilter::Info);
if let Ok(lvl) = std::env::var("RUST_LOG") {
builder.parse_filters(&lvl);
}
builder.parse_filters(pattern);
let isatty = atty::is(atty::Stream::Stderr);
let enable_color = isatty;
builder.format(move |buf, record| {
let now = time::now();
let timestamp =
time::strftime("%Y-%m-%d %H:%M:%S", &now)
.expect("Error formatting log timestamp");
let mut output = if log::max_level() <= log::LevelFilter::Info {
format!("{} {}", Colour::Black.bold().paint(timestamp), record.args())
} else {
let name = ::std::thread::current()
.name()
.map_or_else(Default::default, |x| format!("{}", Colour::Blue.bold().paint(x)));
let millis = (now.tm_nsec as f32 / 1000000.0).round() as usize;
let timestamp = format!("{}.{:03}", timestamp, millis);
format!(
"{} {} {} {} {}",
Colour::Black.bold().paint(timestamp),
name,
record.level(),
record.target(),
record.args()
)
};
if !enable_color {
output = kill_color(output.as_ref());
}
if !isatty && record.level() <= log::Level::Info && atty::is(atty::Stream::Stdout) {
// duplicate INFO/WARN output to console
println!("{}", output);
}
writeln!(buf, "{}", output)
});
if builder.try_init().is_err() {
info!("Not registering Substrate logger, as there is already a global logger registered!");
}
}
fn kill_color(s: &str) -> String {
lazy_static! {
static ref RE: Regex = Regex::new("\x1b\\[[^m]+m").expect("Error initializing color regex");
}
RE.replace_all(s, "").to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use tempdir::TempDir;
use network::config::identity::ed25519;
#[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("long names are not very cool for the ui").is_err());
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());
}
#[test]
fn test_node_key_config_input() {
fn secret_input(net_config_dir: Option) -> error::Result<()> {
NodeKeyType::variants().into_iter().try_for_each(|t| {
let node_key_type = NodeKeyType::from_str(t).unwrap();
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
};
node_key_config(params, &net_config_dir).and_then(|c| match c {
NodeKeyConfig::Ed25519(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(None).is_ok());
assert!(secret_input(Some("x".to_string())).is_ok());
}
#[test]
fn test_node_key_config_file() {
fn secret_file(net_config_dir: Option) -> error::Result<()> {
NodeKeyType::variants().into_iter().try_for_each(|t| {
let node_key_type = NodeKeyType::from_str(t).unwrap();
let tmp = TempDir::new("alice")?;
let file = tmp.path().join(format!("{}_mysecret", t)).to_path_buf();
let params = NodeKeyParams {
node_key_type,
node_key: None,
node_key_file: Some(file.clone())
};
node_key_config(params, &net_config_dir).and_then(|c| match c {
NodeKeyConfig::Ed25519(network::config::Secret::File(ref f))
if node_key_type == NodeKeyType::Ed25519 && f == &file => Ok(()),
_ => Err(error::Error::Input("Unexpected node key config".into()))
})
})
}
assert!(secret_file(None).is_ok());
assert!(secret_file(Some("x".to_string())).is_ok());
}
#[test]
fn test_node_key_config_default() {
fn with_def_params(f: F) -> error::Result<()>
where
F: Fn(NodeKeyParams) -> error::Result<()>
{
NodeKeyType::variants().into_iter().try_for_each(|t| {
let node_key_type = NodeKeyType::from_str(t).unwrap();
f(NodeKeyParams {
node_key_type,
node_key: None,
node_key_file: None
})
})
}
fn no_config_dir() -> error::Result<()> {
with_def_params(|params| {
let typ = params.node_key_type;
node_key_config::(params, &None)
.and_then(|c| match c {
NodeKeyConfig::Ed25519(network::config::Secret::New)
if typ == NodeKeyType::Ed25519 => Ok(()),
_ => Err(error::Error::Input("Unexpected node key config".into()))
})
})
}
fn some_config_dir(net_config_dir: String) -> error::Result<()> {
with_def_params(|params| {
let dir = PathBuf::from(net_config_dir.clone());
let typ = params.node_key_type;
node_key_config(params, &Some(net_config_dir.clone()))
.and_then(move |c| match c {
NodeKeyConfig::Ed25519(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()))
})
})
}
assert!(no_config_dir().is_ok());
assert!(some_config_dir("x".to_string()).is_ok());
}
}