mithril_client_cli/commands/cardano_db/
verify.rs1use std::{
2 collections::HashMap,
3 path::{Path, PathBuf},
4 sync::Arc,
5};
6
7use anyhow::Context;
8use chrono::Utc;
9use clap::Parser;
10use mithril_client::MithrilResult;
11use slog::Logger;
12
13use crate::{
14 commands::{
15 cardano_db::{shared_steps, CardanoDbCommandsBackend},
16 client_builder, SharedArgs,
17 },
18 configuration::{ConfigError, ConfigParameters, ConfigSource},
19 utils::{self, ExpanderUtils, IndicatifFeedbackReceiver, ProgressOutputType, ProgressPrinter},
20 CommandContext,
21};
22
23#[derive(Parser, Debug, Clone)]
25pub struct CardanoDbVerifyCommand {
26 #[arg(short, long, value_enum, default_value_t = CardanoDbCommandsBackend::V2)]
27 backend: CardanoDbCommandsBackend,
28
29 #[clap(flatten)]
30 shared_args: SharedArgs,
31
32 digest: String,
36
37 #[clap(long)]
39 db_dir: Option<PathBuf>,
40
41 #[clap(long, env = "GENESIS_VERIFICATION_KEY")]
43 genesis_verification_key: Option<String>,
44}
45
46impl CardanoDbVerifyCommand {
47 pub fn is_json_output_enabled(&self) -> bool {
49 self.shared_args.json
50 }
51
52 pub async fn execute(&self, context: CommandContext) -> MithrilResult<()> {
54 match self.backend {
55 CardanoDbCommandsBackend::V1 => Err(anyhow::anyhow!(
56 r#"The "verify" subcommand is not available for the v1, use --backend v2 instead"#,
57 )),
58 CardanoDbCommandsBackend::V2 => {
59 let params = context.config_parameters()?.add_source(self)?;
60 self.verify(context.logger(), params).await
61 }
62 }
63 }
64
65 async fn verify(&self, logger: &Logger, params: ConfigParameters) -> MithrilResult<()> {
66 let db_dir = params.require("db_dir")?;
67 let db_dir = Path::new(&db_dir);
68
69 let progress_output_type = if self.is_json_output_enabled() {
70 ProgressOutputType::JsonReporter
71 } else {
72 ProgressOutputType::Tty
73 };
74 let progress_printer = ProgressPrinter::new(progress_output_type, 4);
75 let client = client_builder(¶ms)?
76 .add_feedback_receiver(Arc::new(IndicatifFeedbackReceiver::new(
77 progress_output_type,
78 logger.clone(),
79 )))
80 .with_logger(logger.clone())
81 .build()?;
82
83 client.cardano_database_v2().check_has_immutables(db_dir)?;
84
85 let get_list_of_artifact_ids = || async {
86 let cardano_db_snapshots =
87 client.cardano_database_v2().list().await.with_context(|| {
88 "Can not get the list of artifacts while retrieving the latest cardano db hash"
89 })?;
90
91 Ok(cardano_db_snapshots
92 .iter()
93 .map(|cardano_db| cardano_db.hash.to_owned())
94 .collect::<Vec<String>>())
95 };
96
97 let cardano_db_message = client
98 .cardano_database_v2()
99 .get(
100 &ExpanderUtils::expand_eventual_id_alias(&self.digest, get_list_of_artifact_ids())
101 .await?,
102 )
103 .await?
104 .with_context(|| format!("Can not get the cardano db for hash: '{}'", self.digest))?;
105
106 let certificate = shared_steps::fetch_certificate_and_verifying_chain(
107 1,
108 &progress_printer,
109 &client,
110 &cardano_db_message.certificate_hash,
111 )
112 .await?;
113
114 let immutable_file_range = shared_steps::immutable_file_range(None, None);
115
116 let merkle_proof = shared_steps::compute_verify_merkle_proof(
117 2,
118 &progress_printer,
119 &client,
120 &certificate,
121 &cardano_db_message,
122 &immutable_file_range,
123 db_dir,
124 )
125 .await?;
126
127 let message = shared_steps::compute_cardano_db_snapshot_message(
128 3,
129 &progress_printer,
130 &certificate,
131 &merkle_proof,
132 )
133 .await?;
134
135 shared_steps::verify_message_matches_certificate(
136 logger,
137 4,
138 &progress_printer,
139 &certificate,
140 &message,
141 &cardano_db_message,
142 db_dir,
143 )
144 .await?;
145
146 Self::log_verified_information(
147 db_dir,
148 &cardano_db_message.hash,
149 self.is_json_output_enabled(),
150 )?;
151
152 Ok(())
153 }
154
155 fn log_verified_information(
156 db_dir: &Path,
157 snapshot_hash: &str,
158 json_output: bool,
159 ) -> MithrilResult<()> {
160 if json_output {
161 let canonical_filepath = &db_dir.canonicalize().with_context(|| {
162 format!("Could not get canonical filepath of '{}'", db_dir.display())
163 })?;
164 let json = serde_json::json!({
165 "timestamp": Utc::now().to_rfc3339(),
166 "verified_db_directory": canonical_filepath
167 });
168 println!("{json}");
169 } else {
170 println!("Cardano database snapshot '{snapshot_hash}' archives have been successfully verified. Immutable files have been successfully verified with Mithril.");
171 }
172 Ok(())
173 }
174}
175
176impl ConfigSource for CardanoDbVerifyCommand {
177 fn collect(&self) -> Result<HashMap<String, String>, ConfigError> {
178 let mut map = HashMap::new();
179
180 if let Some(download_dir) = self.db_dir.clone() {
181 let param = "db_dir".to_string();
182 map.insert(
183 param.clone(),
184 utils::path_to_string(&download_dir)
185 .map_err(|e| ConfigError::Conversion(param, e))?,
186 );
187 }
188
189 if let Some(genesis_verification_key) = self.genesis_verification_key.clone() {
190 map.insert(
191 "genesis_verification_key".to_string(),
192 genesis_verification_key,
193 );
194 }
195
196 Ok(map)
197 }
198}