Support KeystoreConfig::InMemory for hardened validator deploys
Context
The torusform hardened-validator deploy model requires that validator session keys (BABE, GRANDPA, im_online, ...) never exist on disk. Keys are injected at runtime via author_insertKey by the torusform-inject daemon after operator approval (see torusform/docs/key-injection-architecture.md for the full flow).
This requires the node to run with its keystore backed by KeystoreConfig::InMemory rather than the default KeystoreConfig::Path { .. }. Upstream sc_cli::KeystoreParams exposes --keystore-path and --password-* but has no "in-memory only" switch — currently the node always creates an on-disk keystore dir, defeating the no-key-on-disk property even if no keys are pre-loaded.
Relevant references in the torusform repo:
specs/validator-nixos-module.md L239 — NixOS systemd unit is written expecting a --keystore-in-memory flag on torus-node.
docs/key-injection-architecture.md L253–263 — canonical description: "The node's keystore must be in-memory".
specs/inject-daemon.md L258 — notes that InMemory keystore drops keys on restart, which is exactly the boot-flow assumption.
Goal
Make torus-node runnable with an in-memory-only keystore when deployed as a hardened validator, so that:
- No keystore directory is ever created on disk (not under
--base-path, not elsewhere).
author_insertKey (via the localhost RPC) is the only path that can populate session keys.
- On restart, keys are gone until
torusform-inject re-injects them.
- The existing dev/testnet workflows (
cargo xtask run local --alice, etc.) are unaffected.
Sketch
Two main options; pick whichever fits cleanest after a closer read of the current node/src/{cli,command,service}.rs:
(A) Add a --keystore-in-memory CLI flag.
- Extend the
Cli struct in node/src/cli.rs with keystore_in_memory: bool (#[arg(long)]).
- In
node/src/command.rs, where the sc_service::Configuration is built for the run subcommand, override config.keystore = KeystoreConfig::InMemory when the flag is set.
sc_service::new_full_parts / new_partial will then use the in-memory backend; rest of the stack is unchanged.
(B) Derive it from role.
- When
config.role.is_authority() AND --validator is active, default keystore to InMemory unconditionally. Keep --keystore-path as an explicit escape hatch for existing workflows.
- Risk: silently changes the default for anyone running
--validator today. Probably wants a feature flag or opt-in env var.
(A) is more conservative and easier to review; (B) is the kind of "secure by default" change that needs separate discussion. Default to (A) unless there's a reason not to.
Acceptance criteria
torus-node --validator --keystore-in-memory ... starts and runs normally.
- Under that invocation, the
--base-path contains no keystore/ directory at any point during the node's lifetime (verified by a test or by inspection during an integration run).
author_insertKey via localhost RPC succeeds and the injected key is usable for block production / finality.
- Node restart with the same base-path results in an empty keystore again (no key material recovered from disk).
- Existing
cargo xtask run local --alice / --bob flows still work unchanged (regression check).
- Unit/integration test covering the InMemory path, even if minimal.
Non-goals
- The full key-injection flow (gate daemon, Vault unwrap, TLS mTLS, operator 2FA) — that's
torusform-inject / torusform-gate territory.
- Keystore encryption-at-rest or hardware-backed key storage — different threat model, separate issue.
- Changing behavior for non-validator roles (api, archive).
References
torusform/docs/key-injection-architecture.md §6 "Substrate Node Configuration"
torusform/specs/validator-nixos-module.md torus.validator module
torusform/specs/inject-daemon.md
- Upstream:
sc_cli::KeystoreParams, sc_service::KeystoreConfig
Support
KeystoreConfig::InMemoryfor hardened validator deploysContext
The torusform hardened-validator deploy model requires that validator session keys (BABE, GRANDPA, im_online, ...) never exist on disk. Keys are injected at runtime via
author_insertKeyby thetorusform-injectdaemon after operator approval (seetorusform/docs/key-injection-architecture.mdfor the full flow).This requires the node to run with its keystore backed by
KeystoreConfig::InMemoryrather than the defaultKeystoreConfig::Path { .. }. Upstreamsc_cli::KeystoreParamsexposes--keystore-pathand--password-*but has no "in-memory only" switch — currently the node always creates an on-disk keystore dir, defeating the no-key-on-disk property even if no keys are pre-loaded.Relevant references in the torusform repo:
specs/validator-nixos-module.mdL239 — NixOS systemd unit is written expecting a--keystore-in-memoryflag ontorus-node.docs/key-injection-architecture.mdL253–263 — canonical description: "The node's keystore must be in-memory".specs/inject-daemon.mdL258 — notes that InMemory keystore drops keys on restart, which is exactly the boot-flow assumption.Goal
Make
torus-noderunnable with an in-memory-only keystore when deployed as a hardened validator, so that:--base-path, not elsewhere).author_insertKey(via the localhost RPC) is the only path that can populate session keys.torusform-injectre-injects them.cargo xtask run local --alice, etc.) are unaffected.Sketch
Two main options; pick whichever fits cleanest after a closer read of the current
node/src/{cli,command,service}.rs:(A) Add a
--keystore-in-memoryCLI flag.Clistruct innode/src/cli.rswithkeystore_in_memory: bool(#[arg(long)]).node/src/command.rs, where thesc_service::Configurationis built for therunsubcommand, overrideconfig.keystore = KeystoreConfig::InMemorywhen the flag is set.sc_service::new_full_parts/new_partialwill then use the in-memory backend; rest of the stack is unchanged.(B) Derive it from role.
config.role.is_authority()AND--validatoris active, default keystore to InMemory unconditionally. Keep--keystore-pathas an explicit escape hatch for existing workflows.--validatortoday. Probably wants a feature flag or opt-in env var.(A) is more conservative and easier to review; (B) is the kind of "secure by default" change that needs separate discussion. Default to (A) unless there's a reason not to.
Acceptance criteria
torus-node --validator --keystore-in-memory ...starts and runs normally.--base-pathcontains nokeystore/directory at any point during the node's lifetime (verified by a test or by inspection during an integration run).author_insertKeyvia localhost RPC succeeds and the injected key is usable for block production / finality.cargo xtask run local --alice/--bobflows still work unchanged (regression check).Non-goals
torusform-inject/torusform-gateterritory.References
torusform/docs/key-injection-architecture.md§6 "Substrate Node Configuration"torusform/specs/validator-nixos-module.mdtorus.validatormoduletorusform/specs/inject-daemon.mdsc_cli::KeystoreParams,sc_service::KeystoreConfig