mithril_aggregator/commands/
genesis_command.rs1use std::{collections::HashMap, path::PathBuf, sync::Arc};
2
3use anyhow::Context;
4use clap::{Parser, Subcommand};
5use config::{ConfigBuilder, Map, Value, builder::DefaultState};
6use serde::{Deserialize, Serialize};
7use slog::{Logger, debug};
8
9use mithril_cardano_node_chain::chain_observer::ChainObserverType;
10use mithril_common::{
11 StdResult,
12 crypto_helper::{
13 ProtocolGenesisSecretKey, ProtocolGenesisSigner, ProtocolGenesisVerificationKey,
14 },
15 entities::{HexEncodedGenesisSecretKey, HexEncodedGenesisVerificationKey},
16};
17use mithril_doc::{Documenter, StructDoc};
18
19use crate::{
20 ConfigurationSource, ExecutionEnvironment, dependency_injection::DependenciesBuilder,
21 extract_all, tools::GenesisTools,
22};
23
24#[derive(Debug, Clone, Serialize, Deserialize, Documenter)]
25pub struct GenesisCommandConfiguration {
26 #[example = "`cardano-cli`"]
28 pub cardano_cli_path: Option<PathBuf>,
29
30 #[example = "`/ipc/node.socket`"]
32 pub cardano_node_socket_path: PathBuf,
33
34 #[example = "`1097911063` or `42`"]
38 pub network_magic: Option<u64>,
39
40 #[example = "`testnet` or `mainnet` or `devnet`"]
42 network: String,
43
44 pub chain_observer_type: ChainObserverType,
46
47 #[example = "`./mithril-aggregator/stores`"]
49 pub data_stores_directory: PathBuf,
50}
51
52impl ConfigurationSource for GenesisCommandConfiguration {
53 fn environment(&self) -> ExecutionEnvironment {
54 ExecutionEnvironment::Production
55 }
56
57 fn cardano_cli_path(&self) -> PathBuf {
58 match self.chain_observer_type {
59 ChainObserverType::CardanoCli => {
60 if let Some(path) = &self.cardano_cli_path {
61 path.clone()
62 } else {
63 panic!("Cardano CLI path must be set when using Cardano CLI chain observer")
64 }
65 }
66 _ => PathBuf::new(),
67 }
68 }
69
70 fn cardano_node_socket_path(&self) -> PathBuf {
71 self.cardano_node_socket_path.clone()
72 }
73
74 fn network_magic(&self) -> Option<u64> {
75 self.network_magic
76 }
77
78 fn network(&self) -> String {
79 self.network.clone()
80 }
81
82 fn chain_observer_type(&self) -> ChainObserverType {
83 self.chain_observer_type.clone()
84 }
85
86 fn data_stores_directory(&self) -> PathBuf {
87 self.data_stores_directory.clone()
88 }
89
90 fn store_retention_limit(&self) -> Option<usize> {
91 None
92 }
93}
94
95#[derive(Parser, Debug, Clone)]
97pub struct GenesisCommand {
98 #[clap(subcommand)]
100 pub genesis_subcommand: GenesisSubCommand,
101}
102
103impl GenesisCommand {
104 pub async fn execute(
105 &self,
106 root_logger: Logger,
107 config_builder: ConfigBuilder<DefaultState>,
108 ) -> StdResult<()> {
109 self.genesis_subcommand.execute(root_logger, config_builder).await
110 }
111
112 pub fn extract_config(command_path: String) -> HashMap<String, StructDoc> {
113 extract_all!(
114 command_path,
115 GenesisSubCommand,
116 Export = { ExportGenesisSubCommand },
117 Import = { ImportGenesisSubCommand },
118 Sign = { SignGenesisSubCommand },
119 Bootstrap = { BootstrapGenesisSubCommand },
120 GenerateKeypair = { GenerateKeypairGenesisSubCommand },
121 )
122 }
123}
124
125#[derive(Debug, Clone, Subcommand)]
127pub enum GenesisSubCommand {
128 Export(ExportGenesisSubCommand),
130
131 Import(ImportGenesisSubCommand),
133
134 Sign(SignGenesisSubCommand),
136
137 Bootstrap(BootstrapGenesisSubCommand),
139
140 GenerateKeypair(GenerateKeypairGenesisSubCommand),
142}
143
144impl GenesisSubCommand {
145 pub async fn execute(
146 &self,
147 root_logger: Logger,
148 config_builder: ConfigBuilder<DefaultState>,
149 ) -> StdResult<()> {
150 match self {
151 Self::Bootstrap(cmd) => cmd.execute(root_logger, config_builder).await,
152 Self::Export(cmd) => cmd.execute(root_logger, config_builder).await,
153 Self::Import(cmd) => cmd.execute(root_logger, config_builder).await,
154 Self::Sign(cmd) => cmd.execute(root_logger).await,
155 Self::GenerateKeypair(cmd) => cmd.execute(root_logger).await,
156 }
157 }
158}
159
160#[derive(Parser, Debug, Clone)]
162pub struct ExportGenesisSubCommand {
163 #[clap(long)]
165 target_path: PathBuf,
166}
167
168impl ExportGenesisSubCommand {
169 pub async fn execute(
170 &self,
171 root_logger: Logger,
172 config_builder: ConfigBuilder<DefaultState>,
173 ) -> StdResult<()> {
174 let config: GenesisCommandConfiguration = config_builder
175 .build()
176 .with_context(|| "configuration build error")?
177 .try_deserialize()
178 .with_context(|| "configuration deserialize error")?;
179 debug!(root_logger, "EXPORT GENESIS command"; "config" => format!("{config:?}"));
180 println!(
181 "Genesis export payload to sign to {}",
182 self.target_path.display()
183 );
184 let mut dependencies_builder =
185 DependenciesBuilder::new(root_logger.clone(), Arc::new(config.clone()));
186 let dependencies = dependencies_builder.create_genesis_container().await.with_context(
187 || "Dependencies Builder can not create genesis command dependencies container",
188 )?;
189
190 let genesis_tools = GenesisTools::from_dependencies(dependencies)
191 .await
192 .with_context(|| "genesis-tools: initialization error")?;
193 genesis_tools
194 .export_payload_to_sign(&self.target_path)
195 .with_context(|| "genesis-tools: export error")?;
196 Ok(())
197 }
198
199 pub fn extract_config(command_path: String) -> HashMap<String, StructDoc> {
200 HashMap::from([(command_path, GenesisCommandConfiguration::extract())])
201 }
202}
203
204#[derive(Parser, Debug, Clone)]
205pub struct ImportGenesisSubCommand {
206 #[clap(long)]
208 signed_payload_path: PathBuf,
209
210 #[clap(long)]
212 genesis_verification_key: HexEncodedGenesisVerificationKey,
213}
214
215impl ImportGenesisSubCommand {
216 pub async fn execute(
217 &self,
218 root_logger: Logger,
219 config_builder: ConfigBuilder<DefaultState>,
220 ) -> StdResult<()> {
221 let config: GenesisCommandConfiguration = config_builder
222 .build()
223 .with_context(|| "configuration build error")?
224 .try_deserialize()
225 .with_context(|| "configuration deserialize error")?;
226 debug!(root_logger, "IMPORT GENESIS command"; "config" => format!("{config:?}"));
227 println!(
228 "Genesis import signed payload from {}",
229 self.signed_payload_path.to_string_lossy()
230 );
231 let mut dependencies_builder =
232 DependenciesBuilder::new(root_logger.clone(), Arc::new(config.clone()));
233 let dependencies = dependencies_builder.create_genesis_container().await.with_context(
234 || "Dependencies Builder can not create genesis command dependencies container",
235 )?;
236
237 let genesis_tools = GenesisTools::from_dependencies(dependencies)
238 .await
239 .with_context(|| "genesis-tools: initialization error")?;
240 genesis_tools
241 .import_payload_signature(
242 &self.signed_payload_path,
243 &ProtocolGenesisVerificationKey::from_json_hex(&self.genesis_verification_key)?,
244 )
245 .await
246 .with_context(|| "genesis-tools: import error")?;
247 Ok(())
248 }
249
250 pub fn extract_config(command_path: String) -> HashMap<String, StructDoc> {
251 HashMap::from([(command_path, GenesisCommandConfiguration::extract())])
252 }
253}
254
255#[derive(Parser, Debug, Clone)]
256pub struct SignGenesisSubCommand {
257 #[clap(long)]
259 to_sign_payload_path: PathBuf,
260
261 #[clap(long)]
263 target_signed_payload_path: PathBuf,
264
265 #[clap(long)]
267 genesis_secret_key_path: PathBuf,
268}
269
270impl SignGenesisSubCommand {
271 pub async fn execute(&self, root_logger: Logger) -> StdResult<()> {
272 debug!(root_logger, "SIGN GENESIS command");
273 println!(
274 "Genesis sign payload from {} to {}",
275 self.to_sign_payload_path.to_string_lossy(),
276 self.target_signed_payload_path.to_string_lossy()
277 );
278
279 GenesisTools::sign_genesis_certificate(
280 &self.to_sign_payload_path,
281 &self.target_signed_payload_path,
282 &self.genesis_secret_key_path,
283 )
284 .await
285 .with_context(|| "genesis-tools: sign error")?;
286
287 Ok(())
288 }
289
290 pub fn extract_config(_command_path: String) -> HashMap<String, StructDoc> {
291 HashMap::new()
292 }
293}
294#[derive(Parser, Debug, Clone)]
295pub struct BootstrapGenesisSubCommand {
296 #[clap(long, env = "GENESIS_SECRET_KEY")]
298 genesis_secret_key: HexEncodedGenesisSecretKey,
299}
300
301impl BootstrapGenesisSubCommand {
302 pub async fn execute(
303 &self,
304 root_logger: Logger,
305 config_builder: ConfigBuilder<DefaultState>,
306 ) -> StdResult<()> {
307 let config: GenesisCommandConfiguration = config_builder
308 .build()
309 .with_context(|| "configuration build error")?
310 .try_deserialize()
311 .with_context(|| "configuration deserialize error")?;
312 debug!(root_logger, "BOOTSTRAP GENESIS command"; "config" => format!("{config:?}"));
313 println!("Genesis bootstrap for test only!");
314 let mut dependencies_builder =
315 DependenciesBuilder::new(root_logger.clone(), Arc::new(config.clone()));
316 let dependencies = dependencies_builder.create_genesis_container().await.with_context(
317 || "Dependencies Builder can not create genesis command dependencies container",
318 )?;
319
320 let genesis_tools = GenesisTools::from_dependencies(dependencies)
321 .await
322 .with_context(|| "genesis-tools: initialization error")?;
323 let genesis_secret_key = ProtocolGenesisSecretKey::from_json_hex(&self.genesis_secret_key)
324 .with_context(|| "json hex decode of genesis secret key failure")?;
325 let genesis_signer = ProtocolGenesisSigner::from_secret_key(genesis_secret_key);
326 genesis_tools
327 .bootstrap_test_genesis_certificate(genesis_signer)
328 .await
329 .with_context(|| "genesis-tools: bootstrap error")?;
330 Ok(())
331 }
332
333 pub fn extract_config(command_path: String) -> HashMap<String, StructDoc> {
334 HashMap::from([(command_path, GenesisCommandConfiguration::extract())])
335 }
336}
337
338#[derive(Parser, Debug, Clone)]
340pub struct GenerateKeypairGenesisSubCommand {
341 #[clap(long)]
343 target_path: PathBuf,
344}
345
346impl GenerateKeypairGenesisSubCommand {
347 pub async fn execute(&self, root_logger: Logger) -> StdResult<()> {
348 debug!(root_logger, "GENERATE KEYPAIR GENESIS command");
349 println!(
350 "Genesis generate keypair to {}",
351 self.target_path.to_string_lossy()
352 );
353
354 GenesisTools::create_and_save_genesis_keypair(&self.target_path)
355 .with_context(|| "genesis-tools: keypair generation error")?;
356
357 Ok(())
358 }
359
360 pub fn extract_config(_parent: String) -> HashMap<String, StructDoc> {
361 HashMap::new()
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use std::sync::Arc;
368
369 use mithril_common::temp_dir;
370
371 use crate::test_tools::TestLogger;
372
373 use super::*;
374
375 #[tokio::test]
376 async fn create_container_does_not_panic() {
377 let config = GenesisCommandConfiguration {
378 cardano_cli_path: None,
379 cardano_node_socket_path: PathBuf::new(),
380 network_magic: Some(42),
381 network: "devnet".to_string(),
382 chain_observer_type: ChainObserverType::Fake,
383 data_stores_directory: temp_dir!().join("stores"),
384 };
385 let mut dependencies_builder =
386 DependenciesBuilder::new(TestLogger::stdout(), Arc::new(config));
387
388 dependencies_builder
389 .create_genesis_container()
390 .await
391 .expect("Expected container creation to succeed without panicking");
392 }
393}