use std::{fs, io::Write, path::{Path, PathBuf}};
use clap::{Args, Parser};
use sc_service::{config::KeystoreConfig, BasePath};
use sp_core::{crypto::{key_types}, ecdsa, Pair};
use core_primitives::{AccountId, AuraId};
use phronesis_runtime::PhronId;
use libp2p::identity::{ed25519 as libp2p_ed25519, PublicKey};
use sc_cli::{KeystoreParams, Error};
use sc_keystore::LocalKeystore;
use sp_keystore::Keystore;
use crate::chain_spec::{ AuthorityKeys, ChainParams, ChainSpec, DEFAULT_BACKUP_FOLDER, SerializablePeerId};
#[derive(Clone, Debug, Args)]
pub struct NodeParams {
#[arg(long, short = 'd', value_name = "PATH")]
base_path: PathBuf,
#[arg(long, default_value = "p2p_secret")]
node_key_file: String,
#[arg(long, default_value = DEFAULT_BACKUP_FOLDER)]
backup_dir: String
}
impl NodeParams {
pub fn base_path(&self) -> BasePath {
BasePath::new(&self.base_path)
}
pub fn node_key_file(&self) -> &str {
&self.node_key_file
}
pub fn backup_dir(&self) -> &str {
&self.backup_dir
}
}
fn aura_key(keystore: &impl Keystore) -> AuraId {
Keystore::sr25519_public_keys(keystore, key_types::AURA)
.pop()
.unwrap_or_else(|| {
Keystore::sr25519_generate_new(keystore, key_types::AURA, None)
.expect("Could not create Aura key")
})
.into()
}
fn phron_key(keystore: &impl Keystore) -> PhronId {
Keystore::ed25519_public_keys(keystore, core_primitives::KEY_TYPE)
.pop()
.unwrap_or_else(|| {
Keystore::ed25519_generate_new(keystore, core_primitives::KEY_TYPE, None)
.expect("Could not create Phron key")
})
.into()
}
fn p2p_key(node_key_path: &Path) -> SerializablePeerId {
if node_key_path.exists() {
let mut file_content =
hex::decode(fs::read(node_key_path).unwrap()).expect("Failed to decode secret as hex");
let secret = libp2p_ed25519::SecretKey::try_from_bytes(&mut file_content)
.expect("Bad node key file");
let keypair = libp2p_ed25519::Keypair::from(secret);
SerializablePeerId::new(PublicKey::from(keypair.public()).to_peer_id())
} else {
let keypair = libp2p_ed25519::Keypair::generate();
let secret = keypair.secret();
let secret_hex = hex::encode(secret.as_ref());
fs::write(node_key_path, secret_hex).expect("Could not write p2p secret");
SerializablePeerId::new(PublicKey::from(keypair.public()).to_peer_id())
}
}
fn backup_path(base_path: &Path, backup_dir: &str) -> PathBuf {
base_path.join(backup_dir)
}
fn open_keystore(
keystore_params: &KeystoreParams,
chain_id: &str,
base_path: &BasePath,
) -> impl Keystore {
let config_dir = base_path.config_dir(chain_id);
match keystore_params
.keystore_config(&config_dir)
.expect("keystore configuration should be available")
{
KeystoreConfig::Path { path, password } => {
LocalKeystore::open(path, password).expect("Keystore open should succeed")
}
_ => unreachable!("keystore_config always returns path and password; qed"),
}
}
fn bootstrap_backup(base_path_with_account_id: &Path, backup_dir: &str) {
let backup_path = backup_path(base_path_with_account_id, backup_dir);
if backup_path.exists() {
if !backup_path.is_dir() {
panic!("Could not create backup directory at {backup_path:?}. Path is already a file.");
}
} else {
fs::create_dir_all(backup_path).expect("Could not create backup directory.");
}
}
fn authority_keys(
keystore: &impl Keystore,
base_path: &Path,
node_key_file: &str,
account_id: AccountId,
) -> AuthorityKeys {
let aura_id = aura_key(keystore);
let phron_id = phron_key(keystore);
let node_key_path = base_path.join(node_key_file);
let peer_id = p2p_key(node_key_path.as_path());
AuthorityKeys {
account_id,
aura_id,
phron_id,
peer_id,
}
}
#[derive(Debug, Parser)]
pub struct BootstrapChainCmd {
#[arg(long = "raw")]
pub raw: bool,
#[clap(flatten)]
pub keystore_params: KeystoreParams,
#[clap(flatten)]
pub chain_params: ChainParams,
#[clap(flatten)]
pub node_params: NodeParams,
}
impl BootstrapChainCmd {
pub fn run(&self) -> Result<(), Error> {
let base_path = self.node_params.base_path();
let backup_dir = self.node_params.backup_dir();
let node_key_file = self.node_params.node_key_file();
let chain_id = self.chain_params.chain_id();
let genesis_authorities = self
.chain_params
.account_ids()
.into_iter()
.map(|account_id| {
let account_base_path: BasePath =
base_path.path().join(account_id.to_string()).into();
bootstrap_backup(account_base_path.path(), backup_dir);
let keystore = open_keystore(&self.keystore_params, chain_id, &account_base_path);
authority_keys(
&keystore,
account_base_path.path(),
node_key_file,
account_id,
)
})
.collect();
let chain_spec = crate::chain_spec::build_chain_spec(self.chain_params.clone(), genesis_authorities)?;
let json = sc_service::chain_ops::build_spec(&chain_spec, self.raw)?;
if std::io::stdout().write_all(json.as_bytes()).is_err() {
let _ = std::io::stderr().write_all(b"Error writing to stdout\n");
}
Ok(())
}
}
#[derive(Debug, Parser)]
pub struct BootstrapNodeCmd {
#[arg(long)]
account_id: Option<String>,
#[arg(long, required_unless_present = "account_id")]
pub account_seed: Option<String>,
#[clap(flatten)]
pub keystore_params: KeystoreParams,
#[clap(flatten)]
pub chain_params: ChainParams,
#[clap(flatten)]
pub node_params: NodeParams,
}
impl BootstrapNodeCmd {
pub fn run(&self) -> Result<(), Error> {
let base_path = self.node_params.base_path();
let backup_dir = self.node_params.backup_dir();
let node_key_file = self.node_params.node_key_file();
bootstrap_backup(base_path.path(), backup_dir);
let chain_id = self.chain_params.chain_id();
let keystore = open_keystore(&self.keystore_params, chain_id, &base_path);
let account_id = self.account_id();
let authority_keys = authority_keys(&keystore, base_path.path(), node_key_file, account_id);
let keys_json = serde_json::to_string_pretty(&authority_keys)
.expect("serialization of authority keys should have succeeded");
println!("{keys_json}");
Ok(())
}
fn account_id(&self) -> AccountId {
match &self.account_id {
Some(id) => {
let hex_string = id.as_str();
let bytes = hex::decode(hex_string).expect("Invalid hex string");
AccountId::from(bytes)
},
None => ecdsa::Public::from(ecdsa::Pair::from_seed(
&sp_core::ecdsa::Pair::generate().1)).into(),
}
}
}
#[derive(Debug, Parser)]
pub struct ConvertChainspecToRawCmd {
#[arg(long)]
pub chain: PathBuf,
}
impl ConvertChainspecToRawCmd {
pub fn run(&self) -> Result<(), Error> {
let spec = ChainSpec::from_json_file(self.chain.to_owned()).expect("Cannot read chainspec");
let raw_chainspec = sc_service::chain_ops::build_spec(&spec, true)?;
if std::io::stdout()
.write_all(raw_chainspec.as_bytes())
.is_err()
{
let _ = std::io::stderr().write_all(b"Error writing to stdout\n");
}
Ok(())
}
}