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