mithril_client_cli/commands/cardano_transaction/
certify.rs1use 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#[derive(Parser, Debug, Clone)]
21pub struct CardanoTransactionsCertifyCommand {
22 #[clap(long, env = "GENESIS_VERIFICATION_KEY")]
24 genesis_verification_key: Option<String>,
25
26 #[clap(value_delimiter = ',', required = true)]
28 transactions_hashes: Vec<String>,
29}
30
31impl CardanoTransactionsCertifyCommand {
32 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}