soil_cli/commands/
generate_node_key.rs1use crate::{build_network_key_dir_or_default, Error, NODE_KEY_ED25519_FILE};
10use clap::{Args, Parser};
11use libp2p_identity::{ed25519, Keypair};
12use soil_service::BasePath;
13use std::{
14 fs,
15 io::{self, Write},
16 path::PathBuf,
17};
18
19#[derive(Debug, Args, Clone)]
21pub struct GenerateKeyCmdCommon {
22 #[arg(long)]
25 file: Option<PathBuf>,
26
27 #[arg(long)]
30 bin: bool,
31}
32
33#[derive(Debug, Clone, Parser)]
35#[command(
36 name = "generate-node-key",
37 about = "Generate a random node key, write it to a file or stdout \
38 and write the corresponding peer-id to stderr"
39)]
40pub struct GenerateNodeKeyCmd {
41 #[clap(flatten)]
42 pub common: GenerateKeyCmdCommon,
43 #[arg(long, value_name = "CHAIN_SPEC")]
47 pub chain: Option<String>,
48 #[arg(long, conflicts_with_all = ["file", "default_base_path"])]
51 base_path: Option<PathBuf>,
52
53 #[arg(long, conflicts_with_all = ["base_path", "file"])]
56 default_base_path: bool,
57}
58
59impl GenerateKeyCmdCommon {
60 pub fn run(&self) -> Result<(), Error> {
62 generate_key(&self.file, self.bin, None, &None, false, None)
63 }
64}
65
66impl GenerateNodeKeyCmd {
67 pub fn run(&self, chain_spec_id: &str, executable_name: &String) -> Result<(), Error> {
69 generate_key(
70 &self.common.file,
71 self.common.bin,
72 Some(chain_spec_id),
73 &self.base_path,
74 self.default_base_path,
75 Some(executable_name),
76 )
77 }
78}
79
80fn generate_key(
85 file: &Option<PathBuf>,
86 bin: bool,
87 chain_spec_id: Option<&str>,
88 base_path: &Option<PathBuf>,
89 default_base_path: bool,
90 executable_name: Option<&String>,
91) -> Result<(), Error> {
92 let keypair = ed25519::Keypair::generate();
93
94 let secret = keypair.secret();
95
96 let file_data = if bin {
97 secret.as_ref().to_owned()
98 } else {
99 array_bytes::bytes2hex("", secret).into_bytes()
100 };
101
102 match (file, base_path, default_base_path) {
103 (Some(file), None, false) => fs::write(file, file_data)?,
104 (None, Some(_), false) | (None, None, true) => {
105 let network_path = build_network_key_dir_or_default(
106 base_path.clone().map(BasePath::new),
107 chain_spec_id.unwrap_or_default(),
108 executable_name.ok_or(Error::Input("Executable name not provided".into()))?,
109 );
110
111 fs::create_dir_all(network_path.as_path())?;
112
113 let key_path = network_path.join(NODE_KEY_ED25519_FILE);
114 if key_path.exists() {
115 eprintln!("Skip generation, a key already exists in {:?}", key_path);
116 return Err(Error::KeyAlreadyExistsInPath(key_path));
117 } else {
118 eprintln!("Generating key in {:?}", key_path);
119 fs::write(key_path, file_data)?
120 }
121 },
122 (None, None, false) => io::stdout().lock().write_all(&file_data)?,
123 (_, _, _) => {
124 return Err(Error::Input("Mutually exclusive arguments provided".into()));
126 },
127 }
128
129 eprintln!("{}", Keypair::from(keypair).public().to_peer_id());
130
131 Ok(())
132}
133
134#[cfg(test)]
135pub mod tests {
136 use crate::DEFAULT_NETWORK_CONFIG_PATH;
137
138 use super::*;
139 use std::io::Read;
140 use tempfile::Builder;
141
142 #[test]
143 fn generate_node_key() {
144 let mut file = Builder::new().prefix("keyfile").tempfile().unwrap();
145 let file_path = file.path().display().to_string();
146 let generate = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", &file_path]);
147 assert!(generate.run("test", &String::from("test")).is_ok());
148 let mut buf = String::new();
149 assert!(file.read_to_string(&mut buf).is_ok());
150 assert!(array_bytes::hex2bytes(&buf).is_ok());
151 }
152
153 #[test]
154 fn generate_node_key_base_path() {
155 let base_dir = Builder::new().prefix("keyfile").tempdir().unwrap();
156 let key_path = base_dir
157 .path()
158 .join("chains/test_id/")
159 .join(DEFAULT_NETWORK_CONFIG_PATH)
160 .join(NODE_KEY_ED25519_FILE);
161 let base_path = base_dir.path().display().to_string();
162 let generate =
163 GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--base-path", &base_path]);
164 assert!(generate.run("test_id", &String::from("test")).is_ok());
165 let buf = fs::read_to_string(key_path.as_path()).unwrap();
166 assert!(array_bytes::hex2bytes(&buf).is_ok());
167
168 assert!(generate.run("test_id", &String::from("test")).is_err());
169 let new_buf = fs::read_to_string(key_path).unwrap();
170 assert_eq!(
171 array_bytes::hex2bytes(&new_buf).unwrap(),
172 array_bytes::hex2bytes(&buf).unwrap()
173 );
174 }
175}