mithril_client_cli/commands/cardano_db/
verify.rs

1use 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/// Clap command to verify a Cardano db and its associated certificate.
23#[derive(Parser, Debug, Clone)]
24pub struct CardanoDbVerifyCommand {
25    ///Backend to use, either: `v1` (default, full database restoration only) or `v2` (full or partial database restoration)
26    #[arg(short, long, value_enum, default_value_t = CardanoDbCommandsBackend::V2)]
27    backend: CardanoDbCommandsBackend,
28
29    /// Digest of the Cardano db snapshot to verify  or `latest` for the latest artifact
30    ///
31    /// Use the `list` command to get that information.
32    digest: String,
33
34    /// Directory from where the immutable will be verified.
35    #[clap(long)]
36    db_dir: Option<PathBuf>,
37
38    /// Genesis verification key to check the certificate chain.
39    #[clap(long, env = "GENESIS_VERIFICATION_KEY")]
40    genesis_verification_key: Option<String>,
41}
42
43impl CardanoDbVerifyCommand {
44    /// Main command execution
45    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}