mithril_client_cli/commands/cardano_transaction/
certify.rs

1use anyhow::{Context, anyhow};
2use clap::Parser;
3use cli_table::{Cell, Table, print_stdout};
4use slog::debug;
5use std::{collections::HashMap, sync::Arc};
6
7use mithril_client::{
8    CardanoTransactionsProofs, MessageBuilder, MithrilCertificate, MithrilResult,
9    VerifiedCardanoTransactions, VerifyCardanoTransactionsProofsError, common::TransactionHash,
10};
11
12use crate::utils::{IndicatifFeedbackReceiver, ProgressOutputType, ProgressPrinter};
13use crate::{
14    CommandContext,
15    commands::client_builder,
16    configuration::{ConfigError, ConfigSource},
17};
18
19/// Clap command to show a given Cardano transaction sets
20#[derive(Parser, Debug, Clone)]
21pub struct CardanoTransactionsCertifyCommand {
22    /// Genesis verification key to check the certificate chain.
23    #[clap(long, env = "GENESIS_VERIFICATION_KEY")]
24    genesis_verification_key: Option<String>,
25
26    /// Hashes of the transactions to certify.
27    #[clap(value_delimiter = ',', required = true)]
28    transactions_hashes: Vec<String>,
29}
30
31impl CardanoTransactionsCertifyCommand {
32    /// Cardano transaction certify command
33    pub async fn execute(&self, mut context: CommandContext) -> MithrilResult<()> {
34        context.config_parameters_mut().add_source(self)?;
35        let logger = context.logger();
36
37        let progress_output_type = if context.is_json_output_enabled() {
38            ProgressOutputType::JsonReporter
39        } else {
40            ProgressOutputType::Tty
41        };
42        let progress_printer = ProgressPrinter::new(progress_output_type, 4);
43        let client = client_builder(context.config_parameters())?
44            .add_feedback_receiver(Arc::new(IndicatifFeedbackReceiver::new(
45                progress_output_type,
46                logger.clone(),
47            )))
48            .with_logger(logger.clone())
49            .build()?;
50
51        progress_printer.report_step(1, "Fetching a proof for the given transactions…")?;
52        let cardano_transaction_proof = client
53            .cardano_transaction()
54            .get_proofs(&self.transactions_hashes)
55            .await
56            .with_context(|| {
57                format!(
58                    "Can not get proof from aggregator, transactions hashes: '{:?}'",
59                    self.transactions_hashes
60                )
61            })?;
62        debug!(logger, "Got Proof from aggregator"; "proof" => ?cardano_transaction_proof);
63
64        let verified_transactions =
65            Self::verify_proof_validity(2, &progress_printer, &cardano_transaction_proof)?;
66
67        progress_printer.report_step(
68            3,
69            "Fetching the associated certificate and verifying the certificate chain…",
70        )?;
71        let certificate = client
72            .certificate()
73            .verify_chain(&cardano_transaction_proof.certificate_hash)
74            .await
75            .with_context(|| {
76                format!(
77                    "Can not verify the certificate chain from certificate_hash: '{}'",
78                    verified_transactions.certificate_hash()
79                )
80            })?;
81
82        Self::verify_proof_match_certificate(
83            4,
84            &progress_printer,
85            &certificate,
86            &verified_transactions,
87        )?;
88
89        Self::log_certify_information(
90            &verified_transactions,
91            &cardano_transaction_proof.non_certified_transactions,
92            context.is_json_output_enabled(),
93        )
94    }
95
96    fn verify_proof_validity(
97        step_number: u16,
98        progress_printer: &ProgressPrinter,
99        cardano_transaction_proof: &CardanoTransactionsProofs,
100    ) -> MithrilResult<VerifiedCardanoTransactions> {
101        progress_printer.report_step(step_number, "Verifying the proof…")?;
102        match cardano_transaction_proof.verify() {
103            Ok(verified_transactions) => Ok(verified_transactions),
104            Err(VerifyCardanoTransactionsProofsError::NoCertifiedTransaction) => Err(anyhow!(
105                "Mithril could not certify any of the given transactions.
106
107Mithril may not have signed those transactions yet, please try again later."
108            )),
109            err => err.with_context(|| "Proof verification failed"),
110        }
111    }
112
113    fn verify_proof_match_certificate(
114        step_number: u16,
115        progress_printer: &ProgressPrinter,
116        certificate: &MithrilCertificate,
117        verified_transactions: &VerifiedCardanoTransactions,
118    ) -> MithrilResult<()> {
119        progress_printer.report_step(
120            step_number,
121            "Verify that the proof is signed in the associated certificate",
122        )?;
123        let message = MessageBuilder::new()
124            .compute_cardano_transactions_proofs_message(certificate, verified_transactions);
125        if !certificate.match_message(&message) {
126            return Err(anyhow!(
127                "Proof and certificate don't match (certificate hash = '{}').",
128                certificate.hash
129            ));
130        }
131
132        Ok(())
133    }
134
135    fn log_certify_information(
136        verified_transactions: &VerifiedCardanoTransactions,
137        non_certified_transactions: &[TransactionHash],
138        json_output: bool,
139    ) -> MithrilResult<()> {
140        if json_output {
141            println!(
142                r#"{{"certified_transactions": {}, "non_certified_transactions": {}}}"#,
143                serde_json::to_string(verified_transactions.certified_transactions())?,
144                serde_json::to_string(non_certified_transactions)?,
145            );
146        } else {
147            println!(
148                r###"Cardano transactions proof has been successfully signed in the associated Mithril certificate."###,
149            );
150
151            if !non_certified_transactions.is_empty() {
152                println!(
153                    r###"
154No proof could be computed for some Cardano transactions. Mithril may not have signed those transactions yet, please try again later."###,
155                );
156            }
157
158            let result_table = verified_transactions
159                .certified_transactions()
160                .iter()
161                .map(|tx| vec![tx.cell(), "✅".cell().justify(cli_table::format::Justify::Center)])
162                .chain(non_certified_transactions.iter().map(|tx| {
163                    vec![tx.cell(), "❌".cell().justify(cli_table::format::Justify::Center)]
164                }))
165                .table()
166                .title(vec!["Transaction Hash", "Certified"]);
167
168            print_stdout(result_table)?
169        }
170
171        Ok(())
172    }
173}
174
175impl ConfigSource for CardanoTransactionsCertifyCommand {
176    fn collect(&self) -> Result<HashMap<String, String>, ConfigError> {
177        let mut map = HashMap::new();
178
179        if let Some(genesis_verification_key) = self.genesis_verification_key.clone() {
180            map.insert(
181                "genesis_verification_key".to_string(),
182                genesis_verification_key,
183            );
184        }
185
186        Ok(map)
187    }
188}