mithril_client_cli/commands/cardano_transaction/
certify.rs

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