From e8dd1205eebcbb499c5d20214037a1638b2710fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bastian=20K=C3=B6cher?= <bkchr@users.noreply.github.com>
Date: Sat, 11 Jan 2020 18:47:26 +0100
Subject: [PATCH] Add test for cli keystore path generation (#4571)

* Add test for cli keystore path generation

* Fix test
---
 substrate/client/chain-spec/src/chain_spec.rs |   2 +-
 substrate/client/cli/src/lib.rs               |  98 ++++++--
 substrate/client/service/src/builder.rs       |  10 +-
 substrate/client/service/src/config.rs        |  23 +-
 substrate/client/service/test/src/lib.rs      |   2 +-
 substrate/primitives/runtime/src/lib.rs       |  10 +
 substrate/utils/browser/src/lib.rs            | 236 +++++++++---------
 7 files changed, 225 insertions(+), 156 deletions(-)

diff --git a/substrate/client/chain-spec/src/chain_spec.rs b/substrate/client/chain-spec/src/chain_spec.rs
index ac6070fdd68..6e58083e8cc 100644
--- a/substrate/client/chain-spec/src/chain_spec.rs
+++ b/substrate/client/chain-spec/src/chain_spec.rs
@@ -228,7 +228,7 @@ impl<G, E> ChainSpec<G, E> {
 		let client_spec = ClientSpec {
 			name: name.to_owned(),
 			id: id.to_owned(),
-			boot_nodes: boot_nodes,
+			boot_nodes,
 			telemetry_endpoints,
 			protocol_id: protocol_id.map(str::to_owned),
 			properties,
diff --git a/substrate/client/cli/src/lib.rs b/substrate/client/cli/src/lib.rs
index 18ba347f6ec..620a90c514d 100644
--- a/substrate/client/cli/src/lib.rs
+++ b/substrate/client/cli/src/lib.rs
@@ -351,7 +351,10 @@ impl<'a> ParseAndPrepareBuildSpec<'a> {
 
 		if spec.boot_nodes().is_empty() && !self.params.disable_default_bootnode {
 			let base_path = base_path(&self.params.shared_params, self.version);
-			let cfg = sc_service::Configuration::<C,_,_>::default_with_spec_and_base_path(spec.clone(), Some(base_path));
+			let cfg = sc_service::Configuration::<C,_,_>::default_with_spec_and_base_path(
+				spec.clone(),
+				Some(base_path),
+			);
 			let node_key = node_key_config(
 				self.params.node_key_params,
 				&Some(cfg.in_chain_config_dir(DEFAULT_NETWORK_CONFIG_PATH).expect("We provided a base_path"))
@@ -750,26 +753,33 @@ fn input_keystore_password() -> Result<String, String> {
 }
 
 /// Fill the password field of the given config instance.
-fn fill_config_keystore_password<C, G, E>(
+fn fill_config_keystore_password_and_path<C, G, E>(
 	config: &mut sc_service::Configuration<C, G, E>,
 	cli: &RunCmd,
 ) -> Result<(), String> {
-	if let KeystoreConfig::Path { password, .. } = &mut config.keystore {
-		*password = if cli.password_interactive {
-			#[cfg(not(target_os = "unknown"))]
-			{
-				Some(input_keystore_password()?.into())
-			}
-			#[cfg(target_os = "unknown")]
-			None
-		} 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
-		};
-	}
+	let password = if cli.password_interactive {
+		#[cfg(not(target_os = "unknown"))]
+		{
+			Some(input_keystore_password()?.into())
+		}
+		#[cfg(target_os = "unknown")]
+		None
+	} 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
+	};
+
+	let path = cli.keystore_path.clone().or(
+		config.in_chain_config_dir(DEFAULT_KEYSTORE_CONFIG_PATH)
+	);
+
+	config.keystore = KeystoreConfig::Path {
+		path: path.ok_or_else(|| "No `base_path` provided to create keystore path!")?,
+		password,
+	};
 
 	Ok(())
 }
@@ -840,7 +850,7 @@ where
 {
 	let mut config = create_config_with_db_path(spec_factory, &cli.shared_params, &version)?;
 
-	fill_config_keystore_password(&mut config, &cli)?;
+	fill_config_keystore_password_and_path(&mut config, &cli)?;
 
 	let is_dev = cli.shared_params.dev;
 	let is_authority = cli.validator || cli.sentry || is_dev || cli.keyring.account.is_some();
@@ -875,12 +885,6 @@ where
 		)?
 	}
 
-	let default_keystore_path = config.in_chain_config_dir(DEFAULT_KEYSTORE_CONFIG_PATH);
-
-	if let KeystoreConfig::Path { path, ..} = &mut config.keystore {
-		*path = cli.keystore_path.or(default_keystore_path);
-	}
-
 	// set sentry mode (i.e. act as an authority but **never** actively participate)
 	config.sentry_mode = cli.sentry;
 
@@ -1205,4 +1209,48 @@ mod tests {
 		assert!(no_config_dir().is_ok());
 		assert!(some_config_dir("x".to_string()).is_ok());
 	}
+
+	#[test]
+	fn keystore_path_is_generated_correctly() {
+		let chain_spec = ChainSpec::from_genesis(
+			"test",
+			"test-id",
+			|| (),
+			Vec::new(),
+			None,
+			None,
+			None,
+			None,
+		);
+
+		let version_info = VersionInfo {
+			name: "test",
+			version: "42",
+			commit: "234234",
+			executable_name: "test",
+			description: "cool test",
+			author: "universe",
+			support_url: "com",
+		};
+
+		for keystore_path in vec![None, Some("/keystore/path")] {
+			let mut run_cmds = RunCmd::from_args();
+			run_cmds.shared_params.base_path = Some(PathBuf::from("/test/path"));
+			run_cmds.keystore_path = keystore_path.clone().map(PathBuf::from);
+
+			let node_config = create_run_node_config::<(), _, _, _>(
+				run_cmds.clone(),
+				|_| Ok(Some(chain_spec.clone())),
+				"test",
+				&version_info,
+			).unwrap();
+
+			let expected_path = match keystore_path {
+				Some(path) => PathBuf::from(path),
+				None => PathBuf::from("/test/path/chains/test-id/keystore"),
+			};
+
+			assert_eq!(expected_path, node_config.keystore.path().unwrap().to_owned());
+		}
+	}
 }
diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs
index 3a3702bc194..6eb0259b792 100644
--- a/substrate/client/service/src/builder.rs
+++ b/substrate/client/service/src/builder.rs
@@ -165,10 +165,11 @@ fn new_full_parts<TBl, TRtApi, TExecDisp, TCfg, TGen, TCSExt>(
 {
 	let keystore = match &config.keystore {
 		KeystoreConfig::Path { path, password } => Keystore::open(
-			path.clone().ok_or("No basepath configured")?,
+			path.clone(),
 			password.clone()
 		)?,
-		KeystoreConfig::InMemory => Keystore::new_in_memory()
+		KeystoreConfig::InMemory => Keystore::new_in_memory(),
+		KeystoreConfig::None => return Err("No keystore config provided!".into()),
 	};
 
 	let executor = NativeExecutor::<TExecDisp>::new(
@@ -289,10 +290,11 @@ where TGen: RuntimeGenesis, TCSExt: Extension {
 	>, Error> {
 		let keystore = match &config.keystore {
 			KeystoreConfig::Path { path, password } => Keystore::open(
-				path.clone().ok_or("No basepath configured")?,
+				path.clone(),
 				password.clone()
 			)?,
-			KeystoreConfig::InMemory => Keystore::new_in_memory()
+			KeystoreConfig::InMemory => Keystore::new_in_memory(),
+			KeystoreConfig::None => return Err("No keystore config provided!".into()),
 		};
 
 		let executor = NativeExecutor::<TExecDisp>::new(
diff --git a/substrate/client/service/src/config.rs b/substrate/client/service/src/config.rs
index 94872129dff..75c62682147 100644
--- a/substrate/client/service/src/config.rs
+++ b/substrate/client/service/src/config.rs
@@ -21,7 +21,7 @@ pub use sc_client_db::{kvdb::KeyValueDB, PruningMode};
 pub use sc_network::config::{ExtTransport, NetworkConfiguration, Roles};
 pub use sc_executor::WasmExecutionMethod;
 
-use std::{path::PathBuf, net::SocketAddr, sync::Arc};
+use std::{path::{PathBuf, Path}, net::SocketAddr, sync::Arc};
 pub use sc_transaction_pool::txpool::Options as TransactionPoolOptions;
 use sc_chain_spec::{ChainSpec, RuntimeGenesis, Extension, NoExtension};
 use sp_core::crypto::Protected;
@@ -107,10 +107,12 @@ pub struct Configuration<C, G, E = NoExtension> {
 /// Configuration of the client keystore.
 #[derive(Clone)]
 pub enum KeystoreConfig {
+	/// No config supplied.
+	None,
 	/// Keystore at a path on-disk. Recommended for native nodes.
 	Path {
-		/// The path of the keystore. Will panic if no path is specified.
-		path: Option<PathBuf>,
+		/// The path of the keystore.
+		path: PathBuf,
 		/// Node keystore's password.
 		password: Option<Protected<String>>
 	},
@@ -118,6 +120,16 @@ pub enum KeystoreConfig {
 	InMemory
 }
 
+impl KeystoreConfig {
+	/// Returns the path for the keystore.
+	pub fn path(&self) -> Option<&Path> {
+		match self {
+			Self::Path { path, .. } => Some(&path),
+			Self::None | Self::InMemory => None,
+		}
+	}
+}
+
 /// Configuration of the database of the client.
 #[derive(Clone)]
 pub enum DatabaseConfig {
@@ -150,10 +162,7 @@ impl<C, G, E> Configuration<C, G, E> where
 			roles: Roles::FULL,
 			transaction_pool: Default::default(),
 			network: Default::default(),
-			keystore: KeystoreConfig::Path {
-				path: config_dir.map(|c| c.join("keystore")),
-				password: None
-			},
+			keystore: KeystoreConfig::None,
 			database: DatabaseConfig::Path {
 				path: Default::default(),
 				cache_size: Default::default(),
diff --git a/substrate/client/service/test/src/lib.rs b/substrate/client/service/test/src/lib.rs
index d76064300c5..06a1edd189a 100644
--- a/substrate/client/service/test/src/lib.rs
+++ b/substrate/client/service/test/src/lib.rs
@@ -174,7 +174,7 @@ fn node_config<G, E: Clone> (
 		transaction_pool: Default::default(),
 		network: network_config,
 		keystore: KeystoreConfig::Path {
-			path: Some(root.join("key")),
+			path: root.join("key"),
 			password: None
 		},
 		config_dir: Some(root.clone()),
diff --git a/substrate/primitives/runtime/src/lib.rs b/substrate/primitives/runtime/src/lib.rs
index 198f55869e9..c010ea0456d 100644
--- a/substrate/primitives/runtime/src/lib.rs
+++ b/substrate/primitives/runtime/src/lib.rs
@@ -165,6 +165,16 @@ impl BuildStorage for sp_core::storage::Storage {
 	}
 }
 
+#[cfg(feature = "std")]
+impl BuildStorage for () {
+	fn assimilate_storage(
+		&self,
+		_: &mut sp_core::storage::Storage,
+	)-> Result<(), String> {
+		Err("`assimilate_storage` not implemented for `()`".into())
+	}
+}
+
 /// Consensus engine unique ID.
 pub type ConsensusEngineId = [u8; 4];
 
diff --git a/substrate/utils/browser/src/lib.rs b/substrate/utils/browser/src/lib.rs
index fd4ad9f69e1..e404c661290 100644
--- a/substrate/utils/browser/src/lib.rs
+++ b/substrate/utils/browser/src/lib.rs
@@ -1,4 +1,4 @@
-// Copyright 2019 Parity Technologies (UK) Ltd.
+// Copyright 2019-2020 Parity Technologies (UK) Ltd.
 // This file is part of Substrate.
 
 // Substrate is free software: you can redistribute it and/or modify
@@ -18,13 +18,13 @@ use futures01::sync::mpsc as mpsc01;
 use log::{debug, info};
 use std::sync::Arc;
 use service::{
-    AbstractService, RpcSession, Roles, Configuration, config::{DatabaseConfig, KeystoreConfig},
-    ChainSpec, RuntimeGenesis
+	AbstractService, RpcSession, Roles, Configuration, config::{DatabaseConfig, KeystoreConfig},
+	ChainSpec, RuntimeGenesis
 };
 use wasm_bindgen::prelude::*;
 use futures::{
-    TryFutureExt as _, FutureExt as _, Stream as _, Future as _, TryStreamExt as _,
-    channel::{oneshot, mpsc}, future::{poll_fn, ok}, compat::*,
+	TryFutureExt as _, FutureExt as _, Stream as _, Future as _, TryStreamExt as _,
+	channel::{oneshot, mpsc}, future::{poll_fn, ok}, compat::*,
 };
 use std::task::Poll;
 use std::pin::Pin;
@@ -38,136 +38,136 @@ pub use console_log::init_with_level as init_console_log;
 ///
 /// This configuration contains good defaults for a browser light client.
 pub async fn browser_configuration<C, G, E>(
-    transport: Transport,
-    chain_spec: ChainSpec<G, E>,
+	transport: Transport,
+	chain_spec: ChainSpec<G, E>,
 ) -> Result<Configuration<C, G, E>, Box<dyn std::error::Error>>
 where
-    C: Default,
-    G: RuntimeGenesis,
-    E: Extension,
+	C: Default,
+	G: RuntimeGenesis,
+	E: Extension,
 {
-    let name = chain_spec.name().to_string();
-
-    let transport = ExtTransport::new(transport);
-    let mut config = Configuration::default_with_spec_and_base_path(chain_spec, None);
-    config.network.transport = network::config::TransportConfig::Normal {
-        wasm_external_transport: Some(transport.clone()),
-        allow_private_ipv4: true,
-        enable_mdns: false,
-    };
-    config.telemetry_external_transport = Some(transport);
-    config.roles = Roles::LIGHT;
-    config.name = format!("{} (Browser)", name);
-    config.database = {
-        info!("Opening Indexed DB database '{}'...", name);
-        let db = kvdb_web::Database::open(name, 10)
-            .await?;
-        DatabaseConfig::Custom(Arc::new(db))
-    };
-    config.keystore = KeystoreConfig::InMemory;
-
-    Ok(config)
+	let name = chain_spec.name().to_string();
+
+	let transport = ExtTransport::new(transport);
+	let mut config = Configuration::default_with_spec_and_base_path(chain_spec, None);
+	config.network.transport = network::config::TransportConfig::Normal {
+		wasm_external_transport: Some(transport.clone()),
+		allow_private_ipv4: true,
+		enable_mdns: false,
+	};
+	config.telemetry_external_transport = Some(transport);
+	config.roles = Roles::LIGHT;
+	config.name = format!("{} (Browser)", name);
+	config.database = {
+		info!("Opening Indexed DB database '{}'...", name);
+		let db = kvdb_web::Database::open(name, 10)
+			.await?;
+		DatabaseConfig::Custom(Arc::new(db))
+	};
+	config.keystore = KeystoreConfig::InMemory;
+
+	Ok(config)
 }
 
 /// A running client.
 #[wasm_bindgen]
 pub struct Client {
-    rpc_send_tx: mpsc::UnboundedSender<RpcMessage>,
+	rpc_send_tx: mpsc::UnboundedSender<RpcMessage>,
 }
 
 struct RpcMessage {
-    rpc_json: String,
-    session: RpcSession,
-    send_back: oneshot::Sender<Pin<Box<dyn futures::Future<Output = Option<String>> + Send>>>,
+	rpc_json: String,
+	session: RpcSession,
+	send_back: oneshot::Sender<Pin<Box<dyn futures::Future<Output = Option<String>> + Send>>>,
 }
 
 /// Create a Client object that connects to a service.
 pub fn start_client(service: impl AbstractService) -> Client {
-    let mut service = service.compat();
-    // We dispatch a background task responsible for processing the service.
-    //
-    // The main action performed by the code below consists in polling the service with
-    // `service.poll()`.
-    // The rest consists in handling RPC requests.
-    let (rpc_send_tx, mut rpc_send_rx) = mpsc::unbounded::<RpcMessage>();
-    wasm_bindgen_futures::spawn_local(poll_fn(move |cx| {
-        loop {
-            match Pin::new(&mut rpc_send_rx).poll_next(cx) {
-                Poll::Ready(Some(message)) => {
-                    let fut = service.get_ref()
-                        .rpc_query(&message.session, &message.rpc_json)
-                        .compat()
-                        .unwrap_or_else(|_| None)
-                        .boxed();
-                    let _ = message.send_back.send(fut);
-                },
-                Poll::Pending => break,
-                Poll::Ready(None) => return Poll::Ready(()),
-            }
-        }
-
-        Pin::new(&mut service)
-            .poll(cx)
-            .map(drop)
-    }));
-
-    Client {
-        rpc_send_tx,
-    }
+	let mut service = service.compat();
+	// We dispatch a background task responsible for processing the service.
+	//
+	// The main action performed by the code below consists in polling the service with
+	// `service.poll()`.
+	// The rest consists in handling RPC requests.
+	let (rpc_send_tx, mut rpc_send_rx) = mpsc::unbounded::<RpcMessage>();
+	wasm_bindgen_futures::spawn_local(poll_fn(move |cx| {
+		loop {
+			match Pin::new(&mut rpc_send_rx).poll_next(cx) {
+				Poll::Ready(Some(message)) => {
+					let fut = service.get_ref()
+						.rpc_query(&message.session, &message.rpc_json)
+						.compat()
+						.unwrap_or_else(|_| None)
+						.boxed();
+					let _ = message.send_back.send(fut);
+				},
+				Poll::Pending => break,
+				Poll::Ready(None) => return Poll::Ready(()),
+			}
+		}
+
+		Pin::new(&mut service)
+			.poll(cx)
+			.map(drop)
+	}));
+
+	Client {
+		rpc_send_tx,
+	}
 }
 
 #[wasm_bindgen]
 impl Client {
-    /// Allows starting an RPC request. Returns a `Promise` containing the result of that request.
-    #[wasm_bindgen(js_name = "rpcSend")]
-    pub fn rpc_send(&mut self, rpc: &str) -> js_sys::Promise {
-        let rpc_session = RpcSession::new(mpsc01::channel(1).0);
-        let (tx, rx) = oneshot::channel();
-        let _ = self.rpc_send_tx.unbounded_send(RpcMessage {
-            rpc_json: rpc.to_owned(),
-            session: rpc_session,
-            send_back: tx,
-        });
-        wasm_bindgen_futures::future_to_promise(async {
-            match rx.await {
-                Ok(fut) => {
-                    fut.await
-                        .map(|s| JsValue::from_str(&s))
-                        .ok_or_else(|| JsValue::NULL)
-                },
-                Err(_) => Err(JsValue::NULL)
-            }
-        })
-    }
-
-    /// Subscribes to an RPC pubsub endpoint.
-    #[wasm_bindgen(js_name = "rpcSubscribe")]
-    pub fn rpc_subscribe(&mut self, rpc: &str, callback: js_sys::Function) {
-        let (tx, rx) = mpsc01::channel(4);
-        let rpc_session = RpcSession::new(tx);
-        let (fut_tx, fut_rx) = oneshot::channel();
-        let _ = self.rpc_send_tx.unbounded_send(RpcMessage {
-            rpc_json: rpc.to_owned(),
-            session: rpc_session.clone(),
-            send_back: fut_tx,
-        });
-        wasm_bindgen_futures::spawn_local(async {
-            if let Ok(fut) = fut_rx.await {
-                fut.await;
-            }
-        });
-
-        wasm_bindgen_futures::spawn_local(async move {
-            let _ = rx.compat()
-                .try_for_each(|s| {
-                    let _ = callback.call1(&callback, &JsValue::from_str(&s));
-                    ok(())
-                })
-                .await;
-
-            // We need to keep `rpc_session` alive.
-            debug!("RPC subscription has ended");
-            drop(rpc_session);
-        });
-    }
+	/// Allows starting an RPC request. Returns a `Promise` containing the result of that request.
+	#[wasm_bindgen(js_name = "rpcSend")]
+	pub fn rpc_send(&mut self, rpc: &str) -> js_sys::Promise {
+		let rpc_session = RpcSession::new(mpsc01::channel(1).0);
+		let (tx, rx) = oneshot::channel();
+		let _ = self.rpc_send_tx.unbounded_send(RpcMessage {
+			rpc_json: rpc.to_owned(),
+			session: rpc_session,
+			send_back: tx,
+		});
+		wasm_bindgen_futures::future_to_promise(async {
+			match rx.await {
+				Ok(fut) => {
+					fut.await
+						.map(|s| JsValue::from_str(&s))
+						.ok_or_else(|| JsValue::NULL)
+				},
+				Err(_) => Err(JsValue::NULL)
+			}
+		})
+	}
+
+	/// Subscribes to an RPC pubsub endpoint.
+	#[wasm_bindgen(js_name = "rpcSubscribe")]
+	pub fn rpc_subscribe(&mut self, rpc: &str, callback: js_sys::Function) {
+		let (tx, rx) = mpsc01::channel(4);
+		let rpc_session = RpcSession::new(tx);
+		let (fut_tx, fut_rx) = oneshot::channel();
+		let _ = self.rpc_send_tx.unbounded_send(RpcMessage {
+			rpc_json: rpc.to_owned(),
+			session: rpc_session.clone(),
+			send_back: fut_tx,
+		});
+		wasm_bindgen_futures::spawn_local(async {
+			if let Ok(fut) = fut_rx.await {
+				fut.await;
+			}
+		});
+
+		wasm_bindgen_futures::spawn_local(async move {
+			let _ = rx.compat()
+				.try_for_each(|s| {
+					let _ = callback.call1(&callback, &JsValue::from_str(&s));
+					ok(())
+				})
+				.await;
+
+			// We need to keep `rpc_session` alive.
+			debug!("RPC subscription has ended");
+			drop(rpc_session);
+		});
+	}
 }
-- 
GitLab