snarkvm_debug/package/
build.rs

1// Copyright (C) 2019-2023 Aleo Systems 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// http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::*;
16
17use snarkvm_utilities::DeserializeExt;
18
19pub struct BuildRequest<N: Network> {
20    program: Program<N>,
21    imports: Vec<Program<N>>,
22    function_name: Identifier<N>,
23}
24
25impl<N: Network> BuildRequest<N> {
26    /// Initializes a new build request.
27    pub const fn new(program: Program<N>, imports: Vec<Program<N>>, function_name: Identifier<N>) -> Self {
28        Self { program, imports, function_name }
29    }
30
31    /// Sends the request to the given endpoint.
32    pub fn send(&self, endpoint: &str) -> Result<BuildResponse<N>> {
33        Ok(ureq::get(endpoint).send_json(self)?.into_json()?)
34    }
35
36    /// Returns the program.
37    pub const fn program(&self) -> &Program<N> {
38        &self.program
39    }
40
41    /// Returns the imports.
42    pub const fn imports(&self) -> &Vec<Program<N>> {
43        &self.imports
44    }
45
46    /// Returns the function name.
47    pub const fn function_name(&self) -> &Identifier<N> {
48        &self.function_name
49    }
50}
51
52impl<N: Network> Serialize for BuildRequest<N> {
53    /// Serializes the build request into string or bytes.
54    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
55        let mut request = serializer.serialize_struct("BuildRequest", 3)?;
56        request.serialize_field("program", &self.program)?;
57        request.serialize_field("imports", &self.imports)?;
58        request.serialize_field("function_name", &self.function_name)?;
59        request.end()
60    }
61}
62
63impl<'de, N: Network> Deserialize<'de> for BuildRequest<N> {
64    /// Deserializes the build request from a string or bytes.
65    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
66        // Parse the request from a string into a value.
67        let mut request = serde_json::Value::deserialize(deserializer)?;
68        // Recover the leaf.
69        Ok(Self::new(
70            // Retrieve the program.
71            DeserializeExt::take_from_value::<D>(&mut request, "program")?,
72            // Retrieve the imports.
73            DeserializeExt::take_from_value::<D>(&mut request, "imports")?,
74            // Retrieve the function name.
75            DeserializeExt::take_from_value::<D>(&mut request, "function_name")?,
76        ))
77    }
78}
79
80pub struct BuildResponse<N: Network> {
81    program_id: ProgramID<N>,
82    function_name: Identifier<N>,
83    proving_key: ProvingKey<N>,
84    verifying_key: VerifyingKey<N>,
85}
86
87impl<N: Network> BuildResponse<N> {
88    /// Initializes a new build response.
89    pub const fn new(
90        program_id: ProgramID<N>,
91        function_name: Identifier<N>,
92        proving_key: ProvingKey<N>,
93        verifying_key: VerifyingKey<N>,
94    ) -> Self {
95        Self { program_id, function_name, proving_key, verifying_key }
96    }
97
98    /// Returns the program ID.
99    pub const fn program_id(&self) -> &ProgramID<N> {
100        &self.program_id
101    }
102
103    /// Returns the function name.
104    pub const fn function_name(&self) -> &Identifier<N> {
105        &self.function_name
106    }
107
108    /// Returns the proving key.
109    pub const fn proving_key(&self) -> &ProvingKey<N> {
110        &self.proving_key
111    }
112
113    /// Returns the verifying key.
114    pub const fn verifying_key(&self) -> &VerifyingKey<N> {
115        &self.verifying_key
116    }
117}
118
119impl<N: Network> Serialize for BuildResponse<N> {
120    /// Serializes the build response into string or bytes.
121    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
122        let mut response = serializer.serialize_struct("BuildResponse", 4)?;
123        response.serialize_field("program_id", &self.program_id)?;
124        response.serialize_field("function_name", &self.function_name)?;
125        response.serialize_field("proving_key", &self.proving_key)?;
126        response.serialize_field("verifying_key", &self.verifying_key)?;
127        response.end()
128    }
129}
130
131impl<'de, N: Network> Deserialize<'de> for BuildResponse<N> {
132    /// Deserializes the build response from a string or bytes.
133    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
134        // Parse the response from a string into a value.
135        let mut response = serde_json::Value::deserialize(deserializer)?;
136        // Recover the leaf.
137        Ok(Self::new(
138            // Retrieve the program ID.
139            DeserializeExt::take_from_value::<D>(&mut response, "program_id")?,
140            // Retrieve the function name.
141            DeserializeExt::take_from_value::<D>(&mut response, "function_name")?,
142            // Retrieve the proving key.
143            DeserializeExt::take_from_value::<D>(&mut response, "proving_key")?,
144            // Retrieve the verifying key.
145            DeserializeExt::take_from_value::<D>(&mut response, "verifying_key")?,
146        ))
147    }
148}
149
150impl<N: Network> Package<N> {
151    /// Builds the package.
152    pub fn build<A: crate::circuit::Aleo<Network = N, BaseField = N::Field>>(
153        &self,
154        endpoint: Option<String>,
155    ) -> Result<()> {
156        // Skip the 'build' if the program is already built.
157        if !self.is_build_required::<A>() {
158            return Ok(());
159        }
160
161        // Retrieve the main program.
162        let program = self.program();
163        // Retrieve the program ID.
164        let program_id = program.id();
165
166        #[cfg(feature = "aleo-cli")]
167        println!("⏳ Compiling '{}'...\n", program_id.to_string().bold());
168
169        // Prepare the build directory.
170        let build_directory = self.build_directory();
171        // Create the build directory if it does not exist.
172        if !build_directory.exists() {
173            std::fs::create_dir_all(&build_directory)?;
174        }
175
176        // Construct the process.
177        let process = self.get_process()?;
178
179        // Retrieve the imported programs.
180        let imported_programs = program
181            .imports()
182            .keys()
183            .map(|program_id| process.get_program(program_id).cloned())
184            .collect::<Result<Vec<_>>>()?;
185
186        // Synthesize each proving and verifying key.
187        for function_name in program.functions().keys() {
188            match endpoint {
189                Some(ref endpoint) => {
190                    // Prepare the request.
191                    let request = BuildRequest::new(program.clone(), imported_programs.clone(), *function_name);
192                    // Load the proving and verifying key.
193                    let response = request.send(endpoint)?;
194                    // Ensure the program ID matches.
195                    ensure!(
196                        response.program_id() == program_id,
197                        "Program ID mismatch: {} != {program_id}",
198                        response.program_id()
199                    );
200                    // Ensure the function name matches.
201                    ensure!(
202                        response.function_name() == function_name,
203                        "Function name mismatch: {} != {function_name}",
204                        response.function_name()
205                    );
206                    // Insert the proving key.
207                    process.insert_proving_key(response.program_id(), function_name, response.proving_key().clone())?;
208                    // Insert the verifying key.
209                    process.insert_verifying_key(
210                        response.program_id(),
211                        function_name,
212                        response.verifying_key().clone(),
213                    )?;
214                }
215                None => process.synthesize_key::<A, _>(program_id, function_name, &mut rand::thread_rng())?,
216            }
217        }
218
219        // Load each function circuit.
220        for function_name in program.functions().keys() {
221            // Retrieve the program.
222            let program = process.get_program(program_id)?;
223            // Retrieve the function from the program.
224            let function = program.get_function(function_name)?;
225            // Save all the prover and verifier files for any function calls that are made.
226            for instruction in function.instructions() {
227                if let Instruction::Call(call) = instruction {
228                    // Retrieve the program and resource.
229                    let (program, resource) = match call.operator() {
230                        CallOperator::Locator(locator) => {
231                            (process.get_program(locator.program_id())?, locator.resource())
232                        }
233                        CallOperator::Resource(resource) => (program, resource),
234                    };
235                    // If this is a function call, save its corresponding prover and verifier files.
236                    if program.contains_function(resource) {
237                        // Set the function name to the resource, in this scope.
238                        let function_name = resource;
239                        // Retrieve the proving key.
240                        let proving_key = process.get_proving_key(program.id(), resource)?;
241                        // Retrieve the verifying key.
242                        let verifying_key = process.get_verifying_key(program.id(), resource)?;
243
244                        // Prepare the build directory for the imported program.
245                        let import_build_directory =
246                            self.build_directory().join(format!("{}-{}", program.id().name(), program.id().network()));
247                        // Create the build directory if it does not exist.
248                        if !import_build_directory.exists() {
249                            std::fs::create_dir_all(&import_build_directory)?;
250                        }
251
252                        // Create the prover.
253                        let _prover = ProverFile::create(&import_build_directory, function_name, proving_key)?;
254                        // Create the verifier.
255                        let _verifier = VerifierFile::create(&import_build_directory, function_name, verifying_key)?;
256                    }
257                }
258            }
259
260            // Retrieve the proving key.
261            let proving_key = process.get_proving_key(program_id, function_name)?;
262            // Retrieve the verifying key.
263            let verifying_key = process.get_verifying_key(program_id, function_name)?;
264
265            // Create the prover.
266            let _prover = ProverFile::create(&build_directory, function_name, proving_key)?;
267            // Create the verifier.
268            let _verifier = VerifierFile::create(&build_directory, function_name, verifying_key)?;
269        }
270
271        // Lastly, write the AVM file.
272        let _avm_file = AVMFile::create(&build_directory, program.clone(), true)?;
273
274        // Ensure the build directory exists.
275        if !self.build_directory().exists() {
276            bail!("Build directory does not exist: {}", self.build_directory().display());
277        }
278
279        #[cfg(feature = "aleo-cli")]
280        println!();
281
282        Ok(())
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    type CurrentAleo = snarkvm_circuit::network::AleoV0;
289
290    #[test]
291    fn test_build() {
292        // Samples a new package at a temporary directory.
293        let (directory, package) = crate::package::test_helpers::sample_token_package();
294
295        // Ensure the build directory does *not* exist.
296        assert!(!package.build_directory().exists());
297        // Build the package.
298        package.build::<CurrentAleo>(None).unwrap();
299        // Ensure the build directory exists.
300        assert!(package.build_directory().exists());
301
302        // Proactively remove the temporary directory (to conserve space).
303        std::fs::remove_dir_all(directory).unwrap();
304    }
305
306    #[test]
307    fn test_build_with_import() {
308        // Samples a new package at a temporary directory.
309        let (directory, package) = crate::package::test_helpers::sample_wallet_package();
310
311        // Ensure the build directory does *not* exist.
312        assert!(!package.build_directory().exists());
313        // Build the package.
314        package.build::<CurrentAleo>(None).unwrap();
315        // Ensure the build directory exists.
316        assert!(package.build_directory().exists());
317
318        // Proactively remove the temporary directory (to conserve space).
319        std::fs::remove_dir_all(directory).unwrap();
320    }
321
322    #[test]
323    #[ignore]
324    fn test_build_with_import_credits() {
325        // Samples a new package at a temporary directory.
326        let (directory, package) = crate::package::test_helpers::sample_transfer_package();
327
328        // Ensure the build directory does *not* exist.
329        assert!(!package.build_directory().exists());
330        // Build the package.
331        package.build::<CurrentAleo>(None).unwrap();
332        // Ensure the build directory exists.
333        assert!(package.build_directory().exists());
334
335        // Proactively remove the temporary directory (to conserve space).
336        std::fs::remove_dir_all(directory).unwrap();
337    }
338}