Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend CLI with session-keys-related commands #154

Open
wants to merge 7 commits into
base: devnet
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ thiserror = { workspace = true }

# Substrate
pallet-im-online = { workspace = true, features = ["std"] }
substrate-prometheus-endpoint = { workspace = true }
sc-basic-authorship = { workspace = true }
sc-chain-spec = { workspace = true }
sc-cli = { workspace = true }
Expand All @@ -36,10 +35,11 @@ sc-consensus-babe = { workspace = true }
sc-consensus-babe-rpc = { workspace = true }
sc-consensus-grandpa = { workspace = true }
sc-consensus-grandpa-rpc = { workspace = true }
sc-consensus-slots = { workspace = true }
sc-consensus-manual-seal = { workspace = true }
sc-consensus-slots = { workspace = true }
sc-executor = { workspace = true }
sc-executor-wasmtime = { workspace = true }
sc-keystore = { workspace = true }
sc-network = { workspace = true }
sc-network-sync = { workspace = true }
sc-offchain = { workspace = true }
Expand All @@ -66,6 +66,7 @@ sp-state-machine = { workspace = true, features = ["default"] }
sp-timestamp = { workspace = true, features = ["default"] }
sp-transaction-pool = { workspace = true, features = ["default"] }
sp-transaction-storage-proof = { workspace = true, features = ["default"] }
substrate-prometheus-endpoint = { workspace = true }
# These dependencies are used for RPC
frame-system-rpc-runtime-api = { workspace = true }
pallet-transaction-payment-rpc = { workspace = true }
Expand Down
7 changes: 7 additions & 0 deletions node/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use self::validator::ValidatorCmd;
use crate::service::EthConfiguration;

mod validator;

/// Available Sealing methods.
#[derive(Copy, Clone, Debug, Default, clap::ValueEnum)]
pub enum Sealing {
Expand Down Expand Up @@ -66,5 +69,9 @@ pub enum Subcommand {
/// Db meta columns information.
FrontierDb(fc_cli::FrontierDbCmd),

/// Return the runtime version.
RuntimeVersion,

/// Validator related commands.
Validator(ValidatorCmd),
}
249 changes: 249 additions & 0 deletions node/src/cli/validator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
use clap::{Parser, Subcommand};
use sc_cli::{CliConfiguration, Error, KeystoreParams, SharedParams, SubstrateCli};
use sc_keystore::LocalKeystore;
use sc_service::config::{BasePath, KeystoreConfig};
use sp_api::{ApiExt, ProvideRuntimeApi};
use sp_blockchain::HeaderBackend;
use sp_core::Bytes;
use sp_keystore::{KeystoreExt, KeystorePtr};
use sp_session::SessionKeys;

use atleta_runtime::opaque;

use crate::service::FullClient;

/// Validator related commands.
#[derive(Debug, clap::Parser)]
pub struct ValidatorCmd {
#[allow(missing_docs)]
#[command(subcommand)]
subcommand: Option<ValidatorSubcommands>,

#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
}

impl ValidatorCmd {
pub fn run<Cli: SubstrateCli>(&self, cli: &Cli, client: &FullClient) -> Result<(), Error> {
match &self.subcommand {
Some(sc) => sc.run(cli, client),
_ => Ok(()),
}
}
}

impl CliConfiguration for ValidatorCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
}

#[derive(Debug, Subcommand)]
pub enum ValidatorSubcommands {
/// Generate session keys and insert them into the keystore.
GenerateSessionKeys(GenerateSessionKeysCmd),

/// Decode session keys.
DecodeSessionKeys(DecodeSessionKeysCmd),

/// Insert a session key into the keystore.
InsertKey(InsertKeyCmd),
}

impl ValidatorSubcommands {
/// Runs the command.
pub fn run<Cli: SubstrateCli>(&self, cli: &Cli, client: &FullClient) -> Result<(), Error> {
match self {
Self::GenerateSessionKeys(cmd) => cmd.run(cli, client),
Self::DecodeSessionKeys(cmd) => cmd.run(),
Self::InsertKey(cmd) => cmd.run(cli),
}
}
}

/// `generate-session-keys` subcommand.
#[derive(Debug, Clone, Parser)]
pub struct GenerateSessionKeysCmd {
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,

#[allow(missing_docs)]
#[clap(flatten)]
pub keystore_params: KeystoreParams,
}

impl GenerateSessionKeysCmd {
/// Run the command
pub fn run<Cli: SubstrateCli>(&self, cli: &Cli, client: &FullClient) -> Result<(), Error> {
let keystore = init_keystore(cli, &self.shared_params, &self.keystore_params)?;
let best_block_hash = client.info().best_hash;
let mut runtime_api = client.runtime_api();

runtime_api.register_extension(KeystoreExt::from(keystore.clone()));

let keys = runtime_api
.generate_session_keys(best_block_hash, None)
.map_err(|api_err| Error::Application(Box::new(api_err).into()))?;

println!("{}", sp_core::bytes::to_hex(&keys, true));

Ok(())
}
}

impl CliConfiguration for GenerateSessionKeysCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}

fn keystore_params(&self) -> Option<&KeystoreParams> {
Some(&self.keystore_params)
}
}

/// `insert-key` subcommand.
#[derive(Debug, Clone, Parser)]
pub struct InsertKeyCmd {
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,

#[allow(missing_docs)]
#[clap(flatten)]
pub keystore_params: KeystoreParams,

/// Key type
#[arg(long)]
pub key_type: String,

/// Secret URI
#[arg(long)]
pub suri: String,
hrls marked this conversation as resolved.
Show resolved Hide resolved

/// Public key
#[arg(long)]
pub public_key: Bytes,
}

impl InsertKeyCmd {
/// Run the command
pub fn run<Cli: SubstrateCli>(&self, cli: &Cli) -> Result<(), Error> {
let keystore = init_keystore(cli, &self.shared_params, &self.keystore_params)?;
let key_type = self.key_type.as_str().try_into().map_err(|_| Error::KeyTypeInvalid)?;
keystore
.insert(key_type, &self.suri, &self.public_key[..])
.map_err(|_| Error::KeystoreOperation)?;

Ok(())
}
}

impl CliConfiguration for InsertKeyCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}

fn keystore_params(&self) -> Option<&KeystoreParams> {
Some(&self.keystore_params)
}
}

fn init_keystore<Cli: SubstrateCli>(
cli: &Cli,
shared_params: &SharedParams,
keystore_params: &KeystoreParams,
) -> Result<KeystorePtr, Error> {
let base_path = shared_params
.base_path()?
.unwrap_or_else(|| BasePath::from_project("", "", &Cli::executable_name()));
hrls marked this conversation as resolved.
Show resolved Hide resolved
let chain_id = shared_params.chain_id(shared_params.is_dev());
let chain_spec = cli.load_spec(&chain_id)?;
let config_dir = base_path.config_dir(chain_spec.id());

match keystore_params.keystore_config(&config_dir)? {
KeystoreConfig::Path { path, password } => Ok(LocalKeystore::open(path, password)?.into()),
_ => unreachable!("keystore_config always returns path and password; qed"),
}
}

/// `decode-session-keys` subcommand.
#[derive(Debug, Clone, Parser)]
pub struct DecodeSessionKeysCmd {
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,

#[allow(missing_docs)]
#[clap(flatten)]
pub keystore_params: KeystoreParams,

/// Hex-encoded session keys.
hrls marked this conversation as resolved.
Show resolved Hide resolved
#[arg(value_name = "SESSION KEYS")]
pub keys: String,
}

impl DecodeSessionKeysCmd {
/// Run the command
pub fn run(&self) -> Result<(), Error> {
for key_line in decode_readable(&self.keys)? {
println!("{}: {}", key_line.0, key_line.1);
}
Ok(())
}
}

fn decode_readable(keys: &str) -> Result<Vec<(String, String)>, Error> {
let bytes: Vec<u8> = sp_core::bytes::from_hex(keys)
.map_err(|convert_err| Error::Application(Box::new(convert_err).into()))?;
let decoded = opaque::SessionKeys::decode_into_raw_public_keys(&bytes)
.ok_or(std::io::Error::new(std::io::ErrorKind::Other, "Error decoding session keys"))?;

Ok(decoded
.into_iter()
.map(|(value, key_id)| {
(
String::from_utf8(key_id.0.to_vec()).expect("KeyTypeId string is valid"),
sp_core::bytes::to_hex(&value, true),
)
})
.collect())
}

impl CliConfiguration for DecodeSessionKeysCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn decoding_session_keys_works() {
let keys = "0xeafcb752d8b82fc872f3b4dcb6c55104b80de3b49796a1e61b9ef310eb5da42dd58e4f129f85b11a2b65d2a8b6cfc3f95c1d43505a1ec25bdcae7ff28471c20140b743a501c25bb8fa033dde00de6c73ebf8c62421d4d1bd67780e8098e8aa23";

assert_eq!(
decode_readable(keys).unwrap(),
vec![
(
"babe".to_string(),
"0xeafcb752d8b82fc872f3b4dcb6c55104b80de3b49796a1e61b9ef310eb5da42d"
.to_string()
),
(
"gran".to_string(),
"0xd58e4f129f85b11a2b65d2a8b6cfc3f95c1d43505a1ec25bdcae7ff28471c201"
.to_string()
),
(
"imon".to_string(),
"0x40b743a501c25bb8fa033dde00de6c73ebf8c62421d4d1bd67780e8098e8aa23"
.to_string()
),
]
)
}
}
25 changes: 8 additions & 17 deletions node/src/command.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,3 @@
// This file is part of Substrate.

// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use futures::TryFutureExt;
// Substrate
use sc_cli::{ChainSpec, SubstrateCli};
Expand Down Expand Up @@ -275,6 +258,14 @@ pub fn run() -> sc_cli::Result<()> {

Ok(())
},
Some(Subcommand::Validator(cmd)) => {
let runner = cli.create_runner(cmd)?;
log::set_max_level(log::LevelFilter::Error);
runner.sync_run(|mut config| {
let (client, ..) = service::new_chain_ops(&mut config, &cli.eth)?;
cmd.run(&cli, &client)
})
},
None => {
let runner = cli.create_runner(&cli.run)?;
runner.run_node_until_exit(|config| async move {
Expand Down