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