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