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