mithril_client_cli/commands/mithril_stake_distribution/
download.rs1use anyhow::Context;
2use clap::Parser;
3use mithril_client::common::SignedEntityTypeDiscriminants;
4use std::sync::Arc;
5use std::{
6 collections::HashMap,
7 path::{Path, PathBuf},
8};
9
10use crate::utils::{self, IndicatifFeedbackReceiver, ProgressOutputType, ProgressPrinter};
11use crate::{
12 CommandContext,
13 commands::client_builder,
14 configuration::{ConfigError, ConfigSource},
15 utils::ExpanderUtils,
16};
17use mithril_client::MithrilResult;
18use mithril_client::{MessageBuilder, RequiredAggregatorCapabilities};
19
20#[derive(Parser, Debug, Clone)]
23pub struct MithrilStakeDistributionDownloadCommand {
24 artifact_hash: String,
26
27 #[clap(long)]
32 download_dir: Option<PathBuf>,
33
34 #[clap(long, env = "GENESIS_VERIFICATION_KEY")]
36 genesis_verification_key: Option<String>,
37}
38
39impl MithrilStakeDistributionDownloadCommand {
40 pub async fn execute(&self, mut context: CommandContext) -> MithrilResult<()> {
42 context.config_parameters_mut().add_source(self)?;
43 let download_dir = context.config_parameters().get_or("download_dir", ".");
44 let download_dir = Path::new(&download_dir);
45 let logger = context.logger();
46
47 let progress_output_type = if context.is_json_output_enabled() {
48 ProgressOutputType::JsonReporter
49 } else {
50 ProgressOutputType::Tty
51 };
52 let progress_printer = ProgressPrinter::new(progress_output_type, 4);
53 let client = client_builder(context.config_parameters())?
54 .with_capabilities(RequiredAggregatorCapabilities::SignedEntityType(
55 SignedEntityTypeDiscriminants::MithrilStakeDistribution,
56 ))
57 .add_feedback_receiver(Arc::new(IndicatifFeedbackReceiver::new(
58 progress_output_type,
59 logger.clone(),
60 )))
61 .with_logger(logger.clone())
62 .build()?;
63
64 let get_list_of_artifact_ids = || async {
65 let mithril_stake_distributions = client.mithril_stake_distribution().list().await.with_context(|| {
66 "Can not get the list of artifacts while retrieving the latest stake distribution hash"
67 })?;
68
69 Ok(mithril_stake_distributions
70 .iter()
71 .map(|msd| msd.hash.to_owned())
72 .collect::<Vec<String>>())
73 };
74 progress_printer.report_step(
75 1,
76 &format!(
77 "Fetching Mithril stake distribution '{}' …",
78 self.artifact_hash
79 ),
80 )?;
81 let mithril_stake_distribution = client
82 .mithril_stake_distribution()
83 .get(
84 &ExpanderUtils::expand_eventual_id_alias(
85 &self.artifact_hash,
86 get_list_of_artifact_ids(),
87 )
88 .await?,
89 )
90 .await?
91 .with_context(|| {
92 format!(
93 "Can not download and verify the artifact for hash: '{}'",
94 self.artifact_hash
95 )
96 })?;
97
98 progress_printer.report_step(
99 2,
100 "Fetching the certificate and verifying the certificate chain…",
101 )?;
102 let certificate = client
103 .certificate()
104 .verify_chain(&mithril_stake_distribution.certificate_hash)
105 .await
106 .with_context(|| {
107 format!(
108 "Can not verify the certificate chain from certificate_hash: '{}'",
109 &mithril_stake_distribution.certificate_hash
110 )
111 })?;
112
113 progress_printer.report_step(
114 3,
115 "Verify that the Mithril stake distribution is signed in the associated certificate",
116 )?;
117 let message = MessageBuilder::new()
118 .compute_mithril_stake_distribution_message(&certificate, &mithril_stake_distribution)
119 .with_context(
120 || "Can not compute the message for the given Mithril stake distribution",
121 )?;
122
123 if !certificate.match_message(&message) {
124 return Err(anyhow::anyhow!(
125 "Certificate and message did not match:\ncertificate_message: '{}'\n computed_message: '{}'",
126 certificate.signed_message,
127 message.compute_hash()
128 ));
129 }
130
131 progress_printer.report_step(4, "Writing fetched Mithril stake distribution to a file")?;
132 if !download_dir.is_dir() {
133 std::fs::create_dir_all(download_dir)?;
134 }
135 let filepath = PathBuf::new().join(download_dir).join(format!(
136 "mithril_stake_distribution-{}.json",
137 mithril_stake_distribution.hash
138 ));
139 std::fs::write(
140 &filepath,
141 serde_json::to_string(&mithril_stake_distribution).with_context(|| {
142 format!(
143 "Can not serialize stake distribution artifact '{mithril_stake_distribution:?}'"
144 )
145 })?,
146 )?;
147
148 if context.is_json_output_enabled() {
149 println!(
150 r#"{{"mithril_stake_distribution_hash": "{}", "filepath": "{}"}}"#,
151 mithril_stake_distribution.hash,
152 filepath.display()
153 );
154 } else {
155 println!(
156 "Mithril stake distribution '{}' has been verified and saved as '{}'.",
157 mithril_stake_distribution.hash,
158 filepath.display()
159 );
160 }
161
162 Ok(())
163 }
164}
165
166impl ConfigSource for MithrilStakeDistributionDownloadCommand {
167 fn collect(&self) -> Result<HashMap<String, String>, ConfigError> {
168 let mut map = HashMap::new();
169
170 if let Some(download_dir) = self.download_dir.clone() {
171 let param = "download_dir".to_string();
172 map.insert(
173 param.clone(),
174 utils::path_to_string(&download_dir)
175 .map_err(|e| ConfigError::Conversion(param, e))?,
176 );
177 }
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}