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