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