zombienet_orchestrator/generators/
keystore.rs1use std::{
2 path::{Path, PathBuf},
3 vec,
4};
5
6use hex::encode;
7use support::{constants::THIS_IS_A_BUG, fs::FileSystem};
8
9use super::errors::GeneratorError;
10use crate::{
11 generators::keystore_key_types::{parse_keystore_key_types, KeystoreKeyType},
12 shared::types::NodeAccounts,
13 ScopedFilesystem,
14};
15
16pub async fn generate<'a, T>(
28 acc: &NodeAccounts,
29 node_files_path: impl AsRef<Path>,
30 scoped_fs: &ScopedFilesystem<'a, T>,
31 asset_hub_polkadot: bool,
32 keystore_key_types: Vec<&str>,
33) -> Result<Vec<PathBuf>, GeneratorError>
34where
35 T: FileSystem,
36{
37 scoped_fs.create_dir_all(node_files_path.as_ref()).await?;
39 let mut filenames = vec![];
40
41 let key_types = parse_keystore_key_types(&keystore_key_types, asset_hub_polkadot);
43
44 let futures: Vec<_> = key_types
45 .iter()
46 .map(|key_type| {
47 let filename = generate_keystore_filename(key_type, acc);
48 let file_path = PathBuf::from(format!(
49 "{}/{}",
50 node_files_path.as_ref().to_string_lossy(),
51 filename
52 ));
53 let content = format!("\"{}\"", acc.seed);
54 (filename, scoped_fs.write(file_path, content))
55 })
56 .collect();
57
58 for (filename, future) in futures {
59 future.await?;
60 filenames.push(PathBuf::from(filename));
61 }
62
63 Ok(filenames)
64}
65
66fn generate_keystore_filename(key_type: &KeystoreKeyType, acc: &NodeAccounts) -> String {
70 let account_key = key_type.scheme.account_key();
71 let pk = acc
72 .accounts
73 .get(account_key)
74 .expect(&format!(
75 "Key '{}' should be set for node {THIS_IS_A_BUG}",
76 account_key
77 ))
78 .public_key
79 .as_str();
80
81 format!("{}{}", encode(&key_type.key_type), pk)
82}
83
84#[cfg(test)]
85mod tests {
86 use std::{collections::HashMap, ffi::OsString, str::FromStr};
87
88 use support::fs::in_memory::{InMemoryFile, InMemoryFileSystem};
89
90 use super::*;
91 use crate::shared::types::{NodeAccount, NodeAccounts};
92
93 fn create_test_accounts() -> NodeAccounts {
94 let mut accounts = HashMap::new();
95 accounts.insert(
96 "sr".to_string(),
97 NodeAccount::new("sr_address", "sr_public_key"),
98 );
99 accounts.insert(
100 "ed".to_string(),
101 NodeAccount::new("ed_address", "ed_public_key"),
102 );
103 accounts.insert(
104 "ec".to_string(),
105 NodeAccount::new("ec_address", "ec_public_key"),
106 );
107 NodeAccounts {
108 seed: "//Alice".to_string(),
109 accounts,
110 }
111 }
112
113 fn create_test_fs() -> InMemoryFileSystem {
114 InMemoryFileSystem::new(HashMap::from([(
115 OsString::from_str("/").unwrap(),
116 InMemoryFile::dir(),
117 )]))
118 }
119
120 #[tokio::test]
121 async fn generate_creates_default_keystore_files_when_no_key_types_specified() {
122 let accounts = create_test_accounts();
123 let fs = create_test_fs();
124 let base_dir = "/tmp/test";
125
126 let scoped_fs = ScopedFilesystem { fs: &fs, base_dir };
127 let key_types: Vec<&str> = vec![];
128
129 let res = generate(&accounts, "node1", &scoped_fs, false, key_types).await;
130 assert!(res.is_ok());
131
132 let filenames = res.unwrap();
133
134 assert!(filenames.len() > 10);
135
136 let filename_strs: Vec<String> = filenames
137 .iter()
138 .map(|p| p.to_string_lossy().to_string())
139 .collect();
140
141 assert!(filename_strs.iter().any(|f| f.starts_with("61757261")));
143 assert!(filename_strs.iter().any(|f| f.starts_with("62616265")));
145 assert!(filename_strs.iter().any(|f| f.starts_with("6772616e")));
147 }
148
149 #[tokio::test]
150 async fn generate_creates_only_specified_keystore_files() {
151 let accounts = create_test_accounts();
152 let fs = create_test_fs();
153 let base_dir = "/tmp/test";
154
155 let scoped_fs = ScopedFilesystem { fs: &fs, base_dir };
156 let key_types = vec!["audi", "gran"];
157
158 let res = generate(&accounts, "node1", &scoped_fs, false, key_types).await;
159
160 assert!(res.is_ok());
161
162 let filenames = res.unwrap();
163 assert_eq!(filenames.len(), 2);
164
165 let filename_strs: Vec<String> = filenames
166 .iter()
167 .map(|p| p.to_string_lossy().to_string())
168 .collect();
169
170 assert!(filename_strs
172 .iter()
173 .any(|f| f.starts_with("61756469") && f.contains("sr_public_key")));
174 assert!(filename_strs
176 .iter()
177 .any(|f| f.starts_with("6772616e") && f.contains("ed_public_key")));
178 }
179
180 #[tokio::test]
181 async fn generate_produces_correct_keystore_files() {
182 struct TestCase {
183 name: &'static str,
184 key_types: Vec<&'static str>,
185 asset_hub_polkadot: bool,
186 expected_prefix: &'static str,
187 expected_public_key: &'static str,
188 }
189
190 let test_cases = vec![
191 TestCase {
192 name: "explicit scheme override (gran_sr)",
193 key_types: vec!["gran_sr"],
194 asset_hub_polkadot: false,
195 expected_prefix: "6772616e", expected_public_key: "sr_public_key",
197 },
198 TestCase {
199 name: "aura with asset_hub_polkadot uses ed",
200 key_types: vec!["aura"],
201 asset_hub_polkadot: true,
202 expected_prefix: "61757261", expected_public_key: "ed_public_key",
204 },
205 TestCase {
206 name: "aura without asset_hub_polkadot uses sr",
207 key_types: vec!["aura"],
208 asset_hub_polkadot: false,
209 expected_prefix: "61757261", expected_public_key: "sr_public_key",
211 },
212 TestCase {
213 name: "custom key type with explicit ec scheme",
214 key_types: vec!["cust_ec"],
215 asset_hub_polkadot: false,
216 expected_prefix: "63757374", expected_public_key: "ec_public_key",
218 },
219 ];
220
221 for tc in test_cases {
222 let accounts = create_test_accounts();
223 let fs = create_test_fs();
224 let scoped_fs = ScopedFilesystem {
225 fs: &fs,
226 base_dir: "/tmp/test",
227 };
228
229 let key_types: Vec<&str> = tc.key_types.clone();
230 let res = generate(
231 &accounts,
232 "node1",
233 &scoped_fs,
234 tc.asset_hub_polkadot,
235 key_types,
236 )
237 .await;
238
239 assert!(
240 res.is_ok(),
241 "[{}] Expected Ok but got: {:?}",
242 tc.name,
243 res.err()
244 );
245 let filenames = res.unwrap();
246
247 assert_eq!(filenames.len(), 1, "[{}] Expected 1 file", tc.name);
248
249 let filename = filenames[0].to_string_lossy().to_string();
250 assert!(
251 filename.starts_with(tc.expected_prefix),
252 "[{}] Expected prefix '{}', got '{}'",
253 tc.name,
254 tc.expected_prefix,
255 filename
256 );
257 assert!(
258 filename.contains(tc.expected_public_key),
259 "[{}] Expected public key '{}' in '{}'",
260 tc.name,
261 tc.expected_public_key,
262 filename
263 );
264 }
265 }
266
267 #[tokio::test]
268 async fn generate_ignores_invalid_key_specs_and_uses_defaults() {
269 let accounts = create_test_accounts();
270 let fs = create_test_fs();
271 let scoped_fs = ScopedFilesystem {
272 fs: &fs,
273 base_dir: "/tmp/test",
274 };
275
276 let key_types = vec![
277 "invalid", "xxx", "audi_xx", ];
281
282 let res = generate(&accounts, "node1", &scoped_fs, false, key_types).await;
283
284 assert!(res.is_ok());
285 let filenames = res.unwrap();
286
287 assert!(filenames.len() > 10);
289 }
290}