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;
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/// Clap command to verify a Cardano db and its associated certificate.
24#[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 of the Cardano db snapshot to verify  or `latest` for the latest artifact
33    ///
34    /// Use the `list` command to get that information.
35    digest: String,
36
37    /// Directory from where the immutable will be verified.
38    #[clap(long)]
39    db_dir: Option<PathBuf>,
40
41    /// Genesis verification key to check the certificate chain.
42    #[clap(long, env = "GENESIS_VERIFICATION_KEY")]
43    genesis_verification_key: Option<String>,
44}
45
46impl CardanoDbVerifyCommand {
47    /// Is JSON output enabled
48    pub fn is_json_output_enabled(&self) -> bool {
49        self.shared_args.json
50    }
51
52    /// Main command execution
53    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(&params)?
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}