mithril_client_cli/commands/cardano_transaction/
certify.rs1use 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#[derive(Parser, Debug, Clone)]
21pub struct CardanoTransactionsCertifyCommand {
22 #[clap(flatten)]
23 shared_args: SharedArgs,
24
25 #[clap(long, env = "GENESIS_VERIFICATION_KEY")]
27 genesis_verification_key: Option<String>,
28
29 #[clap(value_delimiter = ',', required = true)]
31 transactions_hashes: Vec<String>,
32}
33
34impl CardanoTransactionsCertifyCommand {
35 pub fn is_json_output_enabled(&self) -> bool {
37 self.shared_args.json
38 }
39
40 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(¶ms)?
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}