mithril_client_cli/commands/cardano_db/download/
mod.rs1mod v1;
2mod v2;
3
4use v1::PreparedCardanoDbV1Download;
5use v2::PreparedCardanoDbV2Download;
6
7use clap::Parser;
8use std::{collections::HashMap, path::PathBuf};
9
10use crate::{
11 CommandContext,
12 commands::cardano_db::CardanoDbCommandsBackend,
13 configuration::{ConfigError, ConfigSource},
14 utils::{self, JSON_CAUTION_KEY},
15};
16use mithril_client::{MithrilResult, common::ImmutableFileNumber};
17
18const DB_DIRECTORY_NAME: &str = "db";
19
20#[derive(Parser, Debug, Clone)]
22pub struct CardanoDbDownloadCommand {
23 #[arg(short, long, value_enum, default_value_t)]
25 backend: CardanoDbCommandsBackend,
26
27 digest: String,
31
32 #[clap(long)]
37 download_dir: Option<PathBuf>,
38
39 #[clap(long, env = "GENESIS_VERIFICATION_KEY")]
41 genesis_verification_key: Option<String>,
42
43 #[clap(long)]
50 include_ancillary: bool,
51
52 #[clap(long, env = "ANCILLARY_VERIFICATION_KEY")]
54 ancillary_verification_key: Option<String>,
55
56 #[clap(long)]
60 start: Option<ImmutableFileNumber>,
61
62 #[clap(long)]
66 end: Option<ImmutableFileNumber>,
67
68 #[clap(long)]
70 allow_override: bool,
71}
72
73impl CardanoDbDownloadCommand {
74 pub async fn execute(&self, mut context: CommandContext) -> MithrilResult<()> {
76 context.config_parameters_mut().add_source(self)?;
77
78 match self.backend {
79 CardanoDbCommandsBackend::V1 => {
80 let prepared_command = self.prepare_v1(&context)?;
81 prepared_command.execute(&context).await
82 }
83 CardanoDbCommandsBackend::V2 => {
84 let prepared_command = self.prepare_v2(&context)?;
85 prepared_command.execute(&context).await
86 }
87 }
88 }
89
90 fn prepare_v1(&self, context: &CommandContext) -> MithrilResult<PreparedCardanoDbV1Download> {
91 if self.allow_override || self.start.is_some() || self.end.is_some() {
92 self.warn_unused_parameter_with_v1_backend(context);
93 }
94
95 let ancillary_verification_key = if self.include_ancillary {
96 self.warn_ancillary_not_signed_by_mithril(context);
97 Some(context.config_parameters().require("ancillary_verification_key")?)
98 } else {
99 self.warn_fast_bootstrap_not_available(context);
100 None
101 };
102
103 Ok(PreparedCardanoDbV1Download {
104 digest: self.digest.clone(),
105 download_dir: context.config_parameters().require("download_dir")?,
106 include_ancillary: self.include_ancillary,
107 ancillary_verification_key,
108 })
109 }
110
111 fn prepare_v2(&self, context: &CommandContext) -> MithrilResult<PreparedCardanoDbV2Download> {
112 let ancillary_verification_key = if self.include_ancillary {
113 self.warn_ancillary_not_signed_by_mithril(context);
114 Some(context.config_parameters().require("ancillary_verification_key")?)
115 } else {
116 self.warn_fast_bootstrap_not_available(context);
117 None
118 };
119
120 Ok(PreparedCardanoDbV2Download {
121 hash: self.digest.clone(),
122 download_dir: context.config_parameters().require("download_dir")?,
123 start: self.start,
124 end: self.end,
125 include_ancillary: self.include_ancillary,
126 ancillary_verification_key,
127 allow_override: self.allow_override,
128 })
129 }
130
131 fn warn_fast_bootstrap_not_available(&self, context: &CommandContext) {
133 if context.is_json_output_enabled() {
134 let json = serde_json::json!({
135 JSON_CAUTION_KEY: "The fast bootstrap of the Cardano node is not available with the current parameters used in this command",
136 "impact": "The ledger state will be recomputed from genesis at startup of the Cardano node",
137 "solution": {
138 "description": "To activate the fast bootstrap of the Cardano node, add the following parameters to the command:",
139 "parameters": [
140 "--include-ancillary",
141 "--ancillary-verification-key (or environment variable ANCILLARY_VERIFICATION_KEY)"
142 ]
143 },
144 });
145 eprintln!("{json}");
146 } else {
147 eprintln!("The fast bootstrap of the Cardano node is not available with the current parameters used in this command.
148This means that the ledger state will be recomputed from genesis at startup of the Cardano node.
149
150In order to activate the fast bootstrap of the Cardano node, add the following parameters to the command:
151--include-ancillary and --ancillary-verification-key (or environment variable ANCILLARY_VERIFICATION_KEY).
152
153Caution: The ancillary files, including the ledger state, are not currently signed by Mithril.
154As a mitigation, IOG owned keys are used to sign these files.
155For more information, please refer to the network configuration page of the documentation (https://mithril.network/doc/manual/getting-started/network-configurations).");
156 }
157 }
158
159 fn warn_ancillary_not_signed_by_mithril(&self, context: &CommandContext) {
160 let message = "Ancillary verification does not use the Mithril certification: as a mitigation, IOG owned keys are used to sign these files.";
161 if context.is_json_output_enabled() {
162 eprintln!(r#"{{"{JSON_CAUTION_KEY}":"{message}"}}"#);
163 } else {
164 eprintln!("{message}");
165 }
166 }
167
168 fn warn_unused_parameter_with_v1_backend(&self, context: &CommandContext) {
169 let message = "`--start`, `--end`, and `--allow-override` are only available with the `v2` backend. They will be ignored.";
170 if context.is_json_output_enabled() {
171 eprintln!(r#"{{"{JSON_CAUTION_KEY}":"{message}"}}"#);
172 } else {
173 eprintln!("{message}");
174 eprintln!();
176 }
177 }
178}
179
180impl ConfigSource for CardanoDbDownloadCommand {
181 fn collect(&self) -> Result<HashMap<String, String>, ConfigError> {
182 let mut map = HashMap::new();
183
184 if let Some(download_dir) = self.download_dir.clone() {
185 let param = "download_dir".to_string();
186 map.insert(
187 param.clone(),
188 utils::path_to_string(&download_dir)
189 .map_err(|e| ConfigError::Conversion(param, e))?,
190 );
191 }
192
193 if let Some(genesis_verification_key) = self.genesis_verification_key.clone() {
194 map.insert(
195 "genesis_verification_key".to_string(),
196 genesis_verification_key,
197 );
198 }
199
200 if let Some(ancillary_verification_key) = self.ancillary_verification_key.clone() {
201 map.insert(
202 "ancillary_verification_key".to_string(),
203 ancillary_verification_key,
204 );
205 }
206
207 Ok(map)
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use slog::Logger;
214
215 use crate::ConfigParameters;
216
217 use super::*;
218
219 fn dummy_command() -> CardanoDbDownloadCommand {
220 CardanoDbDownloadCommand {
221 backend: Default::default(),
222 digest: "whatever_digest".to_string(),
223 download_dir: Some(std::path::PathBuf::from("whatever_dir")),
224 genesis_verification_key: "whatever".to_string().into(),
225 include_ancillary: true,
226 ancillary_verification_key: "whatever".to_string().into(),
227 start: None,
228 end: None,
229 allow_override: false,
230 }
231 }
232
233 #[tokio::test]
234 async fn ancillary_verification_key_is_mandatory_when_include_ancillary_is_true() {
235 let command = CardanoDbDownloadCommand {
236 include_ancillary: true,
237 ancillary_verification_key: None,
238 ..dummy_command()
239 };
240 let command_context = CommandContext::new(
241 ConfigParameters::default(),
242 false,
243 true,
244 Logger::root(slog::Discard, slog::o!()),
245 );
246
247 let result = command.execute(command_context).await;
248
249 assert!(result.is_err());
250 assert_eq!(
251 result.unwrap_err().to_string(),
252 "Parameter 'ancillary_verification_key' is mandatory."
253 );
254 }
255
256 mod prepare_v1 {
257 use super::*;
258
259 #[test]
260 fn ancillary_verification_key_can_be_read_through_configuration_file() {
261 let command = CardanoDbDownloadCommand {
262 ancillary_verification_key: None,
263 ..dummy_command()
264 };
265 let config = ConfigParameters::new(HashMap::from([(
266 "ancillary_verification_key".to_string(),
267 "value from config".to_string(),
268 )]));
269 let mut command_context =
270 CommandContext::new(config, false, true, Logger::root(slog::Discard, slog::o!()));
271 command_context.config_parameters_mut().add_source(&command).unwrap();
272
273 let result = command.prepare_v1(&command_context);
274
275 assert!(result.is_ok());
276 }
277
278 #[test]
279 fn db_download_dir_is_mandatory_to_execute_command() {
280 let command = CardanoDbDownloadCommand {
281 download_dir: None,
282 ..dummy_command()
283 };
284 let mut command_context = CommandContext::new(
285 ConfigParameters::default(),
286 false,
287 true,
288 Logger::root(slog::Discard, slog::o!()),
289 );
290
291 command_context.config_parameters_mut().add_source(&command).unwrap();
292
293 let result = command.prepare_v1(&command_context);
294
295 assert!(result.is_err());
296 assert_eq!(
297 result.unwrap_err().to_string(),
298 "Parameter 'download_dir' is mandatory."
299 );
300 }
301 }
302
303 mod prepare_v2 {
304 use super::*;
305
306 #[test]
307 fn ancillary_verification_key_can_be_read_through_configuration_file() {
308 let command = CardanoDbDownloadCommand {
309 ancillary_verification_key: None,
310 ..dummy_command()
311 };
312 let config = ConfigParameters::new(HashMap::from([(
313 "ancillary_verification_key".to_string(),
314 "value from config".to_string(),
315 )]));
316 let mut command_context =
317 CommandContext::new(config, false, true, Logger::root(slog::Discard, slog::o!()));
318
319 command_context.config_parameters_mut().add_source(&command).unwrap();
320
321 let result = command.prepare_v2(&command_context);
322
323 assert!(result.is_ok());
324 }
325
326 #[test]
327 fn db_download_dir_is_mandatory_to_execute_command() {
328 let command = CardanoDbDownloadCommand {
329 download_dir: None,
330 ..dummy_command()
331 };
332 let mut command_context = CommandContext::new(
333 ConfigParameters::default(),
334 false,
335 true,
336 Logger::root(slog::Discard, slog::o!()),
337 );
338
339 command_context.config_parameters_mut().add_source(&command).unwrap();
340
341 let result = command.prepare_v2(&command_context);
342
343 assert!(result.is_err());
344 assert_eq!(
345 result.unwrap_err().to_string(),
346 "Parameter 'download_dir' is mandatory."
347 );
348 }
349 }
350}