Skip to main content

snarkvm_parameters/
macros.rs

1// Copyright (c) 2019-2026 Provable Inc.
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(all(not(feature = "wasm"), not(target_env = "sgx")))]
72        fn remote_fetch(buffer: &mut Vec<u8>, url: &str) -> Result<(), $crate::errors::ParameterError> {
73            use std::io::Read;
74
75            #[cfg(not(feature = "no_std_out"))]
76            {
77                use colored::*;
78                let output = format!("{:>15} - Downloading \"{}\"", "Installation", url);
79                println!("{}", output.dimmed());
80            }
81
82            // Retry up to 3 times on transient errors (5xx, 429, IO, timeout).
83            let mut attempts = 3u32;
84            loop {
85                match ureq::get(url).config().max_redirects(10).build().call() {
86                    Ok(mut response) => {
87                        response.body_mut().as_reader().read_to_end(buffer)?;
88                        break;
89                    }
90                    Err(ureq::Error::StatusCode(code)) if attempts > 0 && (code >= 500 || code == 429) => {
91                        attempts -= 1;
92                    }
93                    Err(ureq::Error::Io(_) | ureq::Error::Timeout(_)) if attempts > 0 => {
94                        attempts -= 1;
95                    }
96                    Err(err) => return Err(err.into()),
97                }
98            }
99
100            #[cfg(not(feature = "no_std_out"))]
101            {
102                use colored::*;
103                let size_in_megabytes = buffer.len() as u64 / 1_048_576;
104                let output = format!("{:>15} - Download complete ({} MB)", "Installation", size_in_megabytes);
105                println!("{}", output.dimmed());
106            }
107
108            Ok(())
109        }
110
111        #[cfg(feature = "wasm")]
112        fn remote_fetch(url: &str) -> Result<Vec<u8>, $crate::errors::ParameterError> {
113            // Use the browser's XmlHttpRequest object to download the parameter file synchronously.
114            //
115            // This method blocks the event loop while the parameters are downloaded, and should be
116            // executed in a web worker to prevent the main browser window from freezing.
117            let xhr = web_sys::XmlHttpRequest::new().map_err(|_| {
118                $crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest object not found".to_string())
119            })?;
120
121            // XmlHttpRequest if specified as synchronous cannot use the responseType property. It
122            // cannot thus download bytes directly and enforces a text encoding. To get back the
123            // original binary, a charset that does not corrupt the original bytes must be used.
124            xhr.override_mime_type("octet/binary; charset=ISO-8859-5").unwrap();
125
126            // Initialize and send the request.
127            xhr.open_with_async("GET", url, false).map_err(|_| {
128                $crate::errors::ParameterError::Wasm(
129                    "Download failed - This browser does not support synchronous requests".to_string(),
130                )
131            })?;
132            xhr.send()
133                .map_err(|_| $crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest failed".to_string()))?;
134
135            // Wait for the response in a blocking fashion.
136            if xhr.response().is_ok() && xhr.status().unwrap() == 200 {
137                // Get the text from the response.
138                let rust_text = xhr
139                    .response_text()
140                    .map_err(|_| $crate::errors::ParameterError::Wasm("XMLHttpRequest failed".to_string()))?
141                    .ok_or($crate::errors::ParameterError::Wasm(
142                        "The request was successful but no parameters were received".to_string(),
143                    ))?;
144
145                // Re-encode the text back into bytes using the chosen encoding.
146                use encoding::Encoding;
147                encoding::all::ISO_8859_5
148                    .encode(&rust_text, encoding::EncoderTrap::Strict)
149                    .map_err(|_| $crate::errors::ParameterError::Wasm("Parameter decoding failed".to_string()))
150            } else {
151                Err($crate::errors::ParameterError::Wasm("Download failed - XMLHttpRequest failed".to_string()))
152            }
153        }
154    };
155}
156
157macro_rules! impl_load_bytes_logic_local {
158    ($filepath: expr, $buffer: expr, $expected_size: expr, $expected_checksum: expr) => {
159        // Ensure the size matches.
160        if $expected_size != $buffer.len() {
161            remove_file!($filepath);
162            return Err($crate::errors::ParameterError::SizeMismatch($expected_size, $buffer.len()));
163        }
164
165        // Ensure the checksum matches.
166        let candidate_checksum = checksum!($buffer);
167        if $expected_checksum != candidate_checksum {
168            return checksum_error!($expected_checksum, candidate_checksum);
169        }
170
171        return Ok($buffer.to_vec());
172    };
173}
174
175macro_rules! impl_load_bytes_logic_remote {
176    ($remote_urls: expr, $local_dir: expr, $filename: expr, $metadata: expr, $expected_checksum: expr, $expected_size: expr) => {
177        cfg_if::cfg_if! {
178            if #[cfg(all(feature = "filesystem", not(feature="wasm")))] {
179                // Compose the correct file path for the parameter file.
180                let mut file_path = aleo_std::aleo_dir();
181                file_path.push($local_dir);
182                file_path.push($filename);
183
184                let buffer = if file_path.exists() {
185                    // Attempts to load the parameter file locally with an absolute path.
186                    std::fs::read(&file_path)?
187                } else {
188                    // Downloads the missing parameters and stores it in the local directory for use.
189                    #[cfg(not(feature = "no_std_out"))]
190                    {
191                        use colored::*;
192                        let path = format!("(in {:?})", file_path);
193                        eprintln!(
194                            "\n⚠️  \"{}\" does not exist. Downloading and storing it {}.\n",
195                            $filename, path.dimmed()
196                        );
197                    }
198
199                    // Load remote file
200                    cfg_if::cfg_if!{
201                        if #[cfg(all(not(feature = "wasm"), not(target_env = "sgx")))] {
202                            // Try each URL in order, falling back to the next if one fails.
203                            let remote_urls: &[&str] = &$remote_urls;
204                            let mut buffer = vec![];
205                            let mut last_error: Option<($crate::errors::ParameterError, &str)> = None;
206
207                            for base_url in remote_urls.iter() {
208                                // Remove the previous error (if any).
209                                cfg_if::cfg_if!{
210                                    if #[cfg(feature = "no_std_out")] {
211                                        last_error = None;
212                                    } else {
213                                        use colored::Colorize;
214                                        // If this is a retry, print the previous error as warning.
215                                        if let Some((err, url)) = last_error.take() {
216                                            eprintln!("{:>15} - {err}", "Warning".yellow());
217                                            eprintln!("{:>15} - Failed to fetch from \"{url}\". Trying next source...", "Warning".yellow());
218                                         }
219                                    }
220                                }
221
222                                let url = format!("{}/{}", base_url, $filename);
223                                buffer.clear();
224
225                                match Self::remote_fetch(&mut buffer, &url) {
226                                    Ok(()) => {
227                                        // Ensure the checksum matches.
228                                        let candidate_checksum = checksum!(&buffer);
229                                        if $expected_checksum == candidate_checksum {
230                                            // Success - break out of the loop
231                                            break;
232                                        } else {
233                                            last_error = Some(($crate::errors::ParameterError::ChecksumMismatch(
234                                                $expected_checksum.to_string(),
235                                                candidate_checksum,
236                                            ), base_url));
237                                        }
238                                    }
239                                    Err(err) => {
240                                        last_error = Some((err, base_url));
241                                    }
242                                }
243                            }
244
245                            // If all URLs failed, return the last error.
246                            if let Some((err, _)) = last_error {
247                                return Err(err);
248                            }
249
250                            match Self::store_bytes(&buffer, &file_path) {
251                                Ok(()) => buffer,
252                                Err(_) => {
253                                    eprintln!(
254                                        "\n❗ Error - Failed to store \"{}\" locally. Please download this file manually and ensure it is stored in {:?}.\n",
255                                        $filename, file_path
256                                    );
257                                    buffer
258                                }
259                            }
260                        } else {
261                            return Err($crate::errors::ParameterError::RemoteFetchDisabled);
262                        }
263                    }
264                };
265
266                // Ensure the size matches.
267                if $expected_size != buffer.len() {
268                    remove_file!(file_path);
269                    return Err($crate::errors::ParameterError::SizeMismatch($expected_size, buffer.len()));
270                }
271
272                // Ensure the checksum matches.
273                let candidate_checksum = checksum!(buffer.as_slice());
274                if $expected_checksum != candidate_checksum {
275                    return checksum_error!($expected_checksum, candidate_checksum)
276                }
277                return Ok(buffer);
278            } else {
279                cfg_if::cfg_if! {
280                    if #[cfg(feature = "wasm")] {
281                        // Try each URL in order, falling back to the next if one fails.
282                        let remote_urls: &[&str] = &$remote_urls;
283                        let mut buffer = vec![];
284                        let mut last_error: Option<$crate::errors::ParameterError> = None;
285
286                        for base_url in remote_urls.iter() {
287                            let url = format!("{}/{}", base_url, $filename);
288
289                            match Self::remote_fetch(&url) {
290                                Ok(fetched_buffer) => {
291                                    // Ensure the checksum matches.
292                                    let candidate_checksum = checksum!(&fetched_buffer);
293                                    if $expected_checksum == candidate_checksum {
294                                        buffer = fetched_buffer;
295                                        last_error = None;
296                                        break;
297                                    } else {
298                                        last_error = Some($crate::errors::ParameterError::ChecksumMismatch(
299                                            $expected_checksum.to_string(),
300                                            candidate_checksum,
301                                        ));
302                                    }
303                                }
304                                Err(e) => {
305                                    last_error = Some(e);
306                                }
307                            }
308                        }
309
310                        // If all URLs failed, return the last error.
311                        if let Some(e) = last_error {
312                            return Err(e);
313                        }
314
315                        // Ensure the size matches.
316                        if $expected_size != buffer.len() {
317                            return Err($crate::errors::ParameterError::SizeMismatch($expected_size, buffer.len()));
318                        }
319
320                        return Ok(buffer)
321                    } else {
322                        return Err($crate::errors::ParameterError::FilesystemDisabled);
323                    }
324                }
325            }
326        }
327    }
328}
329
330#[macro_export]
331macro_rules! impl_local {
332    ($name: ident, $local_dir: expr, $fname: tt, "usrs") => {
333        #[derive(Clone, Debug, PartialEq, Eq)]
334        pub struct $name;
335
336        impl $name {
337            pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
338
339            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
340                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
341                let expected_checksum: String = metadata["checksum"].as_str().expect("Failed to parse checksum").to_string();
342                let expected_size: usize = metadata["size"].to_string().parse().expect("Failed to retrieve the file size");
343
344                let _filepath = concat!($local_dir, $fname, ".", "usrs");
345                let buffer = include_bytes!(concat!($local_dir, $fname, ".", "usrs"));
346
347                impl_load_bytes_logic_local!(_filepath, buffer, expected_size, expected_checksum);
348            }
349        }
350
351        paste::item! {
352            #[cfg(test)]
353            #[test]
354            fn [< test_ $fname _usrs >]() {
355                // Print error messages if loading fails. This can be simplified once assert_matches! is stable.
356                if let Err(err) = $name::load_bytes() {
357                    panic!("Failed to load bytes: {err}");
358                }
359            }
360        }
361    };
362    ($name: ident, $local_dir: expr, $fname: tt, $ftype: tt, $credits_version: tt) => {
363        #[derive(Clone, Debug, PartialEq, Eq)]
364        pub struct $name;
365
366        impl $name {
367            pub const METADATA: &'static str = include_str!(concat!($local_dir, $credits_version, "/", $fname, ".metadata"));
368
369            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
370                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
371                let expected_checksum: String =
372                    metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
373                let expected_size: usize =
374                    metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
375
376                let _filepath = concat!($local_dir, $credits_version, "/", $fname, ".", $ftype);
377                let buffer = include_bytes!(concat!($local_dir, $credits_version, "/", $fname, ".", $ftype));
378
379                impl_load_bytes_logic_local!(_filepath, buffer, expected_size, expected_checksum);
380            }
381        }
382
383        paste::item! {
384            #[cfg(test)]
385            #[test]
386            fn [< test_ $credits_version _ $fname _ $ftype >]() {
387                if let Err(err) = $name::load_bytes() {
388                    panic!("Failed to load bytes: {err}");
389                }
390            }
391        }
392    };
393}
394
395#[macro_export]
396macro_rules! impl_remote {
397    ($name: ident, $remote_url: expr, $local_dir: expr, $fname: tt, "usrs") => {
398        pub struct $name;
399
400        impl $name {
401            pub const METADATA: &'static str = include_str!(concat!($local_dir, $fname, ".metadata"));
402
403            impl_store_and_remote_fetch!();
404
405            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
406                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
407                let expected_checksum: String = metadata["checksum"].as_str().expect("Failed to parse checksum").to_string();
408                let expected_size: usize = metadata["size"].to_string().parse().expect("Failed to retrieve the file size");
409
410                // Construct the versioned filename.
411                let filename = match expected_checksum.get(0..7) {
412                    Some(sum) => format!("{}.{}.{}", $fname, "usrs", sum),
413                    _ => format!("{}.{}", $fname, "usrs"),
414                };
415
416                impl_load_bytes_logic_remote!($remote_url, $local_dir, &filename, metadata, expected_checksum, expected_size);
417            }
418        }
419        paste::item! {
420            #[cfg(test)]
421            #[test]
422            fn [< test_ $fname _usrs >]() {
423                assert!($name::load_bytes().is_ok());
424            }
425        }
426    };
427    ($name: ident, $remote_url: expr, $local_dir: expr, $fname: tt, $ftype: tt, $credits_version: tt) => {
428        pub struct $name;
429
430        impl $name {
431            pub const METADATA: &'static str = include_str!(concat!($local_dir, $credits_version, "/", $fname, ".metadata"));
432
433            impl_store_and_remote_fetch!();
434
435            pub fn load_bytes() -> Result<Vec<u8>, $crate::errors::ParameterError> {
436                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
437                let expected_checksum: String =
438                    metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
439                let expected_size: usize =
440                    metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
441
442                // Construct the versioned filename.
443                let filename = match expected_checksum.get(0..7) {
444                    Some(sum) => format!("{}.{}.{}", $fname, $ftype, sum),
445                    _ => format!("{}.{}", $fname, $ftype),
446                };
447
448                impl_load_bytes_logic_remote!($remote_url, $local_dir, &filename, metadata, expected_checksum, expected_size);
449            }
450
451            #[cfg(feature = "wasm")]
452            /// Verify external bytes.
453            pub fn verify_bytes(buffer: &[u8]) -> Result<(), $crate::errors::ParameterError> {
454                let metadata: serde_json::Value = serde_json::from_str(Self::METADATA).expect("Metadata was not well-formatted");
455                let expected_checksum: String =
456                    metadata[concat!($ftype, "_checksum")].as_str().expect("Failed to parse checksum").to_string();
457                let expected_size: usize =
458                    metadata[concat!($ftype, "_size")].to_string().parse().expect("Failed to retrieve the file size");
459
460                // Ensure the size matches.
461                if buffer.len() != expected_size {
462                    return Err($crate::errors::ParameterError::SizeMismatch(expected_size, buffer.len()));
463                }
464
465                // Ensure the checksum matches.
466                let candidate_checksum = checksum!(buffer);
467                if expected_checksum != candidate_checksum {
468                    return checksum_error!(expected_checksum, candidate_checksum);
469                }
470                Ok(())
471            }
472        }
473
474        paste::item! {
475            #[cfg(test)]
476            #[test]
477            fn [< test_ $credits_version _ $fname _ $ftype >]() {
478                if let Err(err) = $name::load_bytes() {
479                    panic!("Failed to load bytes: {err}");
480                }
481            }
482        }
483    };
484}