diff --git a/node/src/cli.rs b/node/src/cli.rs index 0ef45d9..9df3522 100644 --- a/node/src/cli.rs +++ b/node/src/cli.rs @@ -51,6 +51,30 @@ pub struct Cli { #[arg(long, default_value = "aura")] pub consensus: Consensus, + /// Keep session keys only in memory. + /// + /// This disables the on-disk keystore entirely. Keys must be injected at + /// runtime through RPC and are lost on restart. + #[arg( + long, + conflicts_with_all = [ + "alice", + "bob", + "charlie", + "dave", + "dev", + "eve", + "ferdie", + "keystore_path", + "one", + "password", + "password_filename", + "password_interactive", + "two" + ] + )] + pub keystore_in_memory: bool, + #[clap(flatten)] pub run: RunCmd, @@ -93,3 +117,52 @@ pub enum Subcommand { #[command(subcommand)] Benchmark(frame_benchmarking_cli::BenchmarkCmd), } + +#[cfg(test)] +mod tests { + use super::*; + use clap::{Parser, error::ErrorKind}; + + #[test] + fn parses_keystore_in_memory_flag() { + let cli = Cli::try_parse_from([ + "torus-node", + "--chain", + "dev", + "--validator", + "--keystore-in-memory", + ]) + .expect("valid CLI arguments"); + + assert!(cli.keystore_in_memory); + } + + #[test] + fn rejects_keystore_in_memory_with_on_disk_keystore_path() { + let error = Cli::try_parse_from([ + "torus-node", + "--keystore-in-memory", + "--keystore-path", + "/tmp/torus-keystore", + ]) + .expect_err("conflicting keystore options should fail"); + + assert_eq!(error.kind(), ErrorKind::ArgumentConflict); + } + + #[test] + fn rejects_keystore_in_memory_with_dev_key_shortcuts() { + let error = Cli::try_parse_from(["torus-node", "--keystore-in-memory", "--alice"]) + .expect_err("dev key shortcuts should fail"); + + assert_eq!(error.kind(), ErrorKind::ArgumentConflict); + } + + #[test] + fn rejects_keystore_in_memory_with_dev_mode() { + let error = Cli::try_parse_from(["torus-node", "--keystore-in-memory", "--dev"]) + .expect_err("dev mode should fail"); + + assert_eq!(error.kind(), ErrorKind::ArgumentConflict); + } +} diff --git a/node/src/command.rs b/node/src/command.rs index 4c142e8..6f78312 100644 --- a/node/src/command.rs +++ b/node/src/command.rs @@ -18,7 +18,11 @@ use benchmarking::{RemarkBuilder, TransferKeepAliveBuilder, inherent_benchmark_data}; use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory, SUBSTRATE_REFERENCE_HARDWARE}; use futures::TryFutureExt; -use polkadot_sdk::{sc_cli::SubstrateCli, sc_service::PartialComponents, *}; +use polkadot_sdk::{ + sc_cli::SubstrateCli, + sc_service::{Configuration, PartialComponents, config::KeystoreConfig}, + *, +}; use torus_runtime::interface::Block; use crate::{ @@ -29,6 +33,22 @@ use crate::{ mod benchmarking; +fn selected_keystore_config( + keystore_in_memory: bool, + configured: KeystoreConfig, +) -> KeystoreConfig { + if keystore_in_memory { + KeystoreConfig::InMemory + } else { + configured + } +} + +fn apply_keystore_config(mut config: Configuration, keystore_in_memory: bool) -> Configuration { + config.keystore = selected_keystore_config(keystore_in_memory, config.keystore); + config +} + impl SubstrateCli for Cli { fn impl_name() -> String { "Torus Node".into() @@ -223,6 +243,8 @@ pub fn run() -> sc_cli::Result<()> { None => { let runner = cli.create_runner(&cli.run)?; runner.run_node_until_exit(|config| async move { + let config = apply_keystore_config(config, cli.keystore_in_memory); + match config.network.network_backend { sc_network::config::NetworkBackendType::Libp2p => { service::new_full::>( @@ -247,3 +269,34 @@ pub fn run() -> sc_cli::Result<()> { } } } + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + #[test] + fn selected_keystore_config_preserves_configured_keystore_by_default() { + let configured = KeystoreConfig::Path { + path: PathBuf::from("/tmp/torus-keystore"), + password: None, + }; + + let selected = selected_keystore_config(false, configured); + + assert!(matches!(selected, KeystoreConfig::Path { .. })); + } + + #[test] + fn selected_keystore_config_uses_in_memory_when_requested() { + let configured = KeystoreConfig::Path { + path: PathBuf::from("/tmp/torus-keystore"), + password: None, + }; + + let selected = selected_keystore_config(true, configured); + + assert!(matches!(selected, KeystoreConfig::InMemory)); + assert!(selected.path().is_none()); + } +}