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