snarkvm_parameters/
macros.rs

1// Copyright 2024 Aleo Network Foundation
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#[macro_export]
17macro_rules! checksum {
18    ($bytes: expr) => {{
19        use sha2::Digest;
20        hex::encode(&sha2::Sha256::digest($bytes))
21    }};
22}
23
24#[macro_export]
25macro_rules! checksum_error {
26    ($expected: expr, $candidate: expr) => {
27        Err($crate::errors::ParameterError::ChecksumMismatch($expected, $candidate))
28    };
29}
30
31#[macro_export]
32macro_rules! remove_file {
33    ($filepath:expr) => {
34        // Safely remove the corrupt file, if it exists.
35        #[cfg(not(feature = "wasm"))]
36        if std::path::PathBuf::from(&$filepath).exists() {
37            match std::fs::remove_file(&$filepath) {
38                Ok(()) => println!("Removed {:?}. Please retry the command.", $filepath),
39                Err(err) => eprintln!("Failed to remove {:?}: {err}", $filepath),
40            }
41        }
42    };
43}
44
45macro_rules! impl_store_and_remote_fetch {
46    () => {
47        #[cfg(not(feature = "wasm"))]
48        fn store_bytes(buffer: &[u8], file_path: &std::path::Path) -> Result<(), $crate::errors::ParameterError> {
49            use snarkvm_utilities::Write;
50
51            #[cfg(not(feature = "no_std_out"))]
52            {
53                use colored::*;
54                let output = format!("{:>15} - Storing file in {:?}", "Installation", file_path);
55                println!("{}", output.dimmed());
56            }
57
58            // Ensure the folders up to the file path all exist.
59            let mut directory_path = file_path.to_path_buf();
60            directory_path.pop();
61            let _ = std::fs::create_dir_all(directory_path)?;
62
63            // Attempt to write the parameter buffer to a file.
64            match std::fs::File::create(file_path) {
65                Ok(mut file) => file.write_all(&buffer)?,
66                Err(error) => eprintln!("{}", error),
67            }
68            Ok(())
69        }
70
71        #[cfg(not(feature = "wasm"))]
72        fn remote_fetch(buffer: &mut Vec<u8>, url: &str) -> Result<(), $crate::errors::ParameterError> {
73            let mut easy = curl::easy::Easy::new();
74            easy.follow_location(true)?;
75            easy.url(url)?;
76
77            #[cfg(not(feature = "no_std_out"))]
78            {
79                use colored::*;
80
81                let output = format!("{:>15} - Downloading \"{}\"", "Installation", url);
82                println!("{}", output.dimmed());
83
84                easy.progress(true)?;
85                easy.progress_function(|total_download, current_download, _, _| {
86                    let percent = (current_download / total_download) * 100.0;
87                    let size_in_megabytes = total_download as u64 / 1_048_576;
88                    let output = format!(
89                        "\r{:>15} - {:.2}% complete ({:#} MB total)",
90                        "Installation", percent, size_in_megabytes
91                    );
92                    print!("{}", output.dimmed());
93                    true
94                })?;
95            }
96
97            let mut transfer = easy.transfer();
98            transfer.write_function(|data| {
99                buffer.extend_from_slice(data);
100                Ok(data.len())
101            })?;
102            Ok(transfer.perform()?)
103        }
104
105        #[cfg(feature = "wasm")]
106        fn remote_fetch(url: &str) -> Result<Vec<u8>, $crate::errors::ParameterError> {
107            // Use the browser's XmlHttpRequest object to download the parameter file synchronously.
108            //
109            // This method blocks the event loop while the parameters are downloaded, and should be
110            // executed in a web worker to prevent the main browser window from freezing.
111            let xhr = web_sys::XmlHttpRequest::new().map_err(|_| {
112                $crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest object not found".to_string())
113            })?;
114
115            // XmlHttpRequest if specified as synchronous cannot use the responseType property. It
116            // cannot thus download bytes directly and enforces a text encoding. To get back the
117            // original binary, a charset that does not corrupt the original bytes must be used.
118            xhr.override_mime_type("octet/binary; charset=ISO-8859-5").unwrap();
119
120            // Initialize and send the request.
121            xhr.open_with_async("GET", url, false).map_err(|_| {
122                $crate::errors::ParameterError::Wasm(
123                    "Download failed - This browser does not support synchronous requests".to_string(),
124                )
125            })?;
126            xhr.send().map_err(|_| {
127                $crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest failed".to_string())
128            })?;
129
130            // Wait for the response in a blocking fashion.
131            if xhr.response().is_ok() && xhr.status().unwrap() == 200 {
132                // Get the text from the response.
133                let rust_text = xhr
134                    .response_text()
135                    .map_err(|_| $crate::errors::ParameterError::Wasm("XMLHttpRequest failed".to_string()))?
136                    .ok_or($crate::errors::ParameterError::Wasm(
137                        "The request was successful but no parameters were received".to_string(),
138                    ))?;
139
140                // Re-encode the text back into bytes using the chosen encoding.
141                use encoding::Encoding;
142                encoding::all::ISO_8859_5
143                    .encode(&rust_text, encoding::EncoderTrap::Strict)
144                    .map_err(|_| $crate::errors::ParameterError::Wasm("Parameter decoding failed".to_string()))
145            } else {
146                Err($crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest failed".to_string()))
147            }
148        }
149    };
150}
151
152macro_rules! impl_load_bytes_logic_local {
153    ($filepath: expr, $buffer: expr, $expected_size: expr, $expected_checksum: expr) => {
154        // Ensure the size matches.
155        if $expected_size != $buffer.len() {
156            remove_file!($filepath);
157            return Err($crate::errors::ParameterError::SizeMismatch($expected_size, $buffer.len()));
158        }
159
160        // Ensure the checksum matches.
161        let candidate_checksum = checksum!($buffer);
162        if $expected_checksum != candidate_checksum {
163            return checksum_error!($expected_checksum, candidate_checksum);
164        }
165
166        return Ok($buffer.to_vec());
167    };
168}
169
170macro_rules! impl_load_bytes_logic_remote {
171    ($remote_url: expr, $local_dir: expr, $filename: expr, $metadata: expr, $expected_checksum: expr, $expected_size: expr) => {
172        // Compose the correct file path for the parameter file.
173        let mut file_path = aleo_std::aleo_dir();
174        file_path.push($local_dir);
175        file_path.push($filename);
176
177        let buffer = if file_path.exists() {
178            // Attempts to load the parameter file locally with an absolute path.
179            std::fs::read(&file_path)?
180        } else {
181            // Downloads the missing parameters and stores it in the local directory for use.
182             #[cfg(not(feature = "no_std_out"))]
183            {
184                use colored::*;
185                let path = format!("(in {:?})", file_path);
186                eprintln!(
187                    "\n⚠️  \"{}\" does not exist. Downloading and storing it {}.\n",
188                    $filename, path.dimmed()
189                );
190            }
191
192            // Construct the URL.
193            let url = format!("{}/{}", $remote_url, $filename);
194
195            // Load remote file
196            cfg_if::cfg_if! {
197                if #[cfg(not(feature = "wasm"))] {
198                    let mut buffer = vec![];
199                    Self::remote_fetch(&mut buffer, &url)?;
200
201                    // Ensure the checksum matches.
202                    let candidate_checksum = checksum!(&buffer);
203                    if $expected_checksum != candidate_checksum {
204                        return checksum_error!($expected_checksum, candidate_checksum)
205                    }
206
207                    match Self::store_bytes(&buffer, &file_path) {
208                        Ok(()) => buffer,
209                        Err(_) => {
210                            eprintln!(
211                                "\n❗ Error - Failed to store \"{}\" locally. Please download this file manually and ensure it is stored in {:?}.\n",
212                                $filename, file_path
213                            );
214                            buffer
215                        }
216                    }
217                } else if #[cfg(feature = "wasm")] {
218                    let buffer = Self::remote_fetch(&url)?;
219
220                    // Ensure the checksum matches.
221                    let candidate_checksum = checksum!(&buffer);
222                    if $expected_checksum != candidate_checksum {
223                        return checksum_error!($expected_checksum, candidate_checksum)
224                    }
225
226                    buffer
227                } else {
228                    return Err($crate::errors::ParameterError::RemoteFetchDisabled);
229                }
230            }
231        };
232
233        // Ensure the size matches.
234        if $expected_size != buffer.len() {
235            remove_file!(file_path);
236            return Err($crate::errors::ParameterError::SizeMismatch($expected_size, buffer.len()));
237        }
238
239        // Ensure the checksum matches.
240        let candidate_checksum = checksum!(buffer.as_slice());
241        if $expected_checksum != candidate_checksum {
242            return checksum_error!($expected_checksum, candidate_checksum)
243        }
244
245        return Ok(buffer)
246    }
247}
248
249#[macro_export]
250macro_rules! impl_local {
251    ($name: ident, $local_dir: expr, $fname: tt, "usrs") => {
252        #[derive(Clone, Debug, PartialEq, Eq)]
253        pub struct $name;
254
255        impl $name {
256            pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
257
258            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
259                let metadata: serde_json::Value =
260                    serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
261                let expected_checksum: String =
262                    metadata["checksum"].as_str().expect("Failed to parse checksum").to_string();
263                let expected_size: usize =
264                    metadata["size"].to_string().parse().expect("Failed to retrieve the file size");
265
266                let _filepath = concat!($local_dir, $fname, ".", "usrs");
267                let buffer = include_bytes!(concat!($local_dir, $fname, ".", "usrs"));
268
269                impl_load_bytes_logic_local!(_filepath, buffer, expected_size, expected_checksum);
270            }
271        }
272
273        paste::item! {
274            #[cfg(test)]
275            #[test]
276            fn [< test_ $fname _usrs >]() {
277                assert!($name::load_bytes().is_ok());
278            }
279        }
280    };
281    ($name: ident, $local_dir: expr, $fname: tt, $ftype: tt) => {
282        #[derive(Clone, Debug, PartialEq, Eq)]
283        pub struct $name;
284
285        impl $name {
286            pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
287
288            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
289                let metadata: serde_json::Value =
290                    serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
291                let expected_checksum: String =
292                    metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
293                let expected_size: usize =
294                    metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
295
296                let _filepath = concat!($local_dir, $fname, ".", $ftype);
297                let buffer = include_bytes!(concat!($local_dir, $fname, ".", $ftype));
298
299                impl_load_bytes_logic_local!(_filepath, buffer, expected_size, expected_checksum);
300            }
301        }
302
303        paste::item! {
304            #[cfg(test)]
305            #[test]
306            fn [< test_ $fname _ $ftype >]() {
307                assert!($name::load_bytes().is_ok());
308            }
309        }
310    };
311}
312
313#[macro_export]
314macro_rules! impl_remote {
315    ($name: ident, $remote_url: expr, $local_dir: expr, $fname: tt, "usrs") => {
316        pub struct $name;
317
318        impl $name {
319            pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
320
321            impl_store_and_remote_fetch!();
322
323            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
324                let metadata: serde_json::Value =
325                    serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
326                let expected_checksum: String =
327                    metadata["checksum"].as_str().expect("Failed to parse checksum").to_string();
328                let expected_size: usize =
329                    metadata["size"].to_string().parse().expect("Failed to retrieve the file size");
330
331                // Construct the versioned filename.
332                let filename = match expected_checksum.get(0..7) {
333                    Some(sum) => format!("{}.{}.{}", $fname, "usrs", sum),
334                    _ => format!("{}.{}", $fname, "usrs"),
335                };
336
337                impl_load_bytes_logic_remote!(
338                    $remote_url,
339                    $local_dir,
340                    &filename,
341                    metadata,
342                    expected_checksum,
343                    expected_size
344                );
345            }
346        }
347        paste::item! {
348            #[cfg(test)]
349            #[test]
350            fn [< test_ $fname _usrs >]() {
351                assert!($name::load_bytes().is_ok());
352            }
353        }
354    };
355    ($name: ident, $remote_url: expr, $local_dir: expr, $fname: tt, $ftype: tt) => {
356        pub struct $name;
357
358        impl $name {
359            pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
360
361            impl_store_and_remote_fetch!();
362
363            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
364                let metadata: serde_json::Value =
365                    serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
366                let expected_checksum: String =
367                    metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
368                let expected_size: usize =
369                    metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
370
371                // Construct the versioned filename.
372                let filename = match expected_checksum.get(0..7) {
373                    Some(sum) => format!("{}.{}.{}", $fname, $ftype, sum),
374                    _ => format!("{}.{}", $fname, $ftype),
375                };
376
377                impl_load_bytes_logic_remote!(
378                    $remote_url,
379                    $local_dir,
380                    &filename,
381                    metadata,
382                    expected_checksum,
383                    expected_size
384                );
385            }
386        }
387
388        paste::item! {
389            #[cfg(test)]
390            #[test]
391            fn [< test_ $fname _ $ftype >]() {
392                assert!($name::load_bytes().is_ok());
393            }
394        }
395    };
396}