mithril_client_cli/commands/cardano_transaction/
certify.rs

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