snarkvm_synthesizer_debug/vm/
execute.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
15#![allow(clippy::too_many_arguments)]
16
17use super::*;
18
19impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
20    /// Returns a new execute transaction.
21    ///
22    /// If a `fee_record` is provided, then a private fee will be included in the transaction;
23    /// otherwise, a public fee will be included in the transaction.
24    ///
25    /// The `priority_fee_in_microcredits` is an additional fee **on top** of the execution fee.
26    pub fn execute<R: Rng + CryptoRng>(
27        &self,
28        private_key: &PrivateKey<N>,
29        (program_id, function_name): (impl TryInto<ProgramID<N>>, impl TryInto<Identifier<N>>),
30        inputs: impl ExactSizeIterator<Item = impl TryInto<Value<N>>>,
31        fee_record: Option<Record<N, Plaintext<N>>>,
32        priority_fee_in_microcredits: u64,
33        query: Option<Query<N, C::BlockStorage>>,
34        rng: &mut R,
35    ) -> Result<Transaction<N>> {
36        // Compute the authorization.
37        let authorization = self.authorize(private_key, program_id, function_name, inputs, rng)?;
38        // Determine if a fee is required.
39        let is_fee_required = !authorization.is_split();
40        // Determine if a priority fee is declared.
41        let is_priority_fee_declared = priority_fee_in_microcredits > 0;
42        // Compute the execution.
43        let execution = self.execute_authorization_raw(authorization, query.clone(), rng)?;
44        // Compute the fee.
45        let fee = match is_fee_required || is_priority_fee_declared {
46            true => {
47                // Compute the minimum execution cost.
48                let (minimum_execution_cost, (_, _)) = execution_cost(self, &execution)?;
49                // Compute the execution ID.
50                let execution_id = execution.to_execution_id()?;
51                // Authorize the fee.
52                let authorization = match fee_record {
53                    Some(record) => self.authorize_fee_private(
54                        private_key,
55                        record,
56                        minimum_execution_cost,
57                        priority_fee_in_microcredits,
58                        execution_id,
59                        rng,
60                    )?,
61                    None => self.authorize_fee_public(
62                        private_key,
63                        minimum_execution_cost,
64                        priority_fee_in_microcredits,
65                        execution_id,
66                        rng,
67                    )?,
68                };
69                // Execute the fee.
70                Some(self.execute_fee_authorization_raw(authorization, query, rng)?)
71            }
72            false => None,
73        };
74        // Return the execute transaction.
75        Transaction::from_execution(execution, fee)
76    }
77
78    /// Returns a new execute transaction for the given authorization.
79    pub fn execute_authorization<R: Rng + CryptoRng>(
80        &self,
81        execute_authorization: Authorization<N>,
82        fee_authorization: Option<Authorization<N>>,
83        query: Option<Query<N, C::BlockStorage>>,
84        rng: &mut R,
85    ) -> Result<Transaction<N>> {
86        // Compute the execution.
87        let execution = self.execute_authorization_raw(execute_authorization, query.clone(), rng)?;
88        // Compute the fee.
89        let fee = match fee_authorization {
90            Some(authorization) => Some(self.execute_fee_authorization_raw(authorization, query, rng)?),
91            None => None,
92        };
93        // Return the execute transaction.
94        Transaction::from_execution(execution, fee)
95    }
96
97    /// Returns a new fee for the given authorization.
98    pub fn execute_fee_authorization<R: Rng + CryptoRng>(
99        &self,
100        authorization: Authorization<N>,
101        query: Option<Query<N, C::BlockStorage>>,
102        rng: &mut R,
103    ) -> Result<Fee<N>> {
104        debug_assert!(authorization.is_fee_private() || authorization.is_fee_public(), "Expected a fee authorization");
105        self.execute_fee_authorization_raw(authorization, query, rng)
106    }
107}
108
109impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
110    /// Executes a call to the program function for the given authorization.
111    /// Returns the execution.
112    #[inline]
113    fn execute_authorization_raw<R: Rng + CryptoRng>(
114        &self,
115        authorization: Authorization<N>,
116        query: Option<Query<N, C::BlockStorage>>,
117        rng: &mut R,
118    ) -> Result<Execution<N>> {
119        let timer = timer!("VM::execute_authorization_raw");
120
121        // Construct the locator of the main function.
122        let locator = {
123            let request = authorization.peek_next()?;
124            Locator::new(*request.program_id(), *request.function_name()).to_string()
125        };
126        // Prepare the query.
127        let query = match query {
128            Some(query) => query,
129            None => Query::VM(self.block_store().clone()),
130        };
131        lap!(timer, "Prepare the query");
132
133        macro_rules! logic {
134            ($process:expr, $network:path, $aleo:path) => {{
135                // Prepare the authorization.
136                let authorization = cast_ref!(authorization as Authorization<$network>);
137                // Execute the call.
138                let (_, mut trace) = $process.execute::<$aleo, _>(authorization.clone(), rng)?;
139                lap!(timer, "Execute the call");
140
141                // Prepare the assignments.
142                cast_mut_ref!(trace as Trace<N>).prepare(query)?;
143                lap!(timer, "Prepare the assignments");
144
145                // Compute the proof and construct the execution.
146                let execution = trace.prove_execution::<$aleo, _>(&locator, rng)?;
147                lap!(timer, "Compute the proof");
148
149                // Return the execution.
150                Ok(cast_ref!(execution as Execution<N>).clone())
151            }};
152        }
153
154        // Execute the authorization.
155        let result = process!(self, logic);
156        finish!(timer, "Execute the authorization");
157        result
158    }
159
160    /// Executes a call to the program function for the given fee authorization.
161    /// Returns the fee.
162    #[inline]
163    fn execute_fee_authorization_raw<R: Rng + CryptoRng>(
164        &self,
165        authorization: Authorization<N>,
166        query: Option<Query<N, C::BlockStorage>>,
167        rng: &mut R,
168    ) -> Result<Fee<N>> {
169        let timer = timer!("VM::execute_fee_authorization_raw");
170
171        // Prepare the query.
172        let query = match query {
173            Some(query) => query,
174            None => Query::VM(self.block_store().clone()),
175        };
176        lap!(timer, "Prepare the query");
177
178        macro_rules! logic {
179            ($process:expr, $network:path, $aleo:path) => {{
180                // Prepare the authorization.
181                let authorization = cast_ref!(authorization as Authorization<$network>);
182                // Execute the call.
183                let (_, mut trace) = $process.execute::<$aleo, _>(authorization.clone(), rng)?;
184                lap!(timer, "Execute the call");
185
186                // Prepare the assignments.
187                cast_mut_ref!(trace as Trace<N>).prepare(query)?;
188                lap!(timer, "Prepare the assignments");
189
190                // Compute the proof and construct the fee.
191                let fee = trace.prove_fee::<$aleo, _>(rng)?;
192                lap!(timer, "Compute the proof");
193
194                // Return the fee.
195                Ok(cast_ref!(fee as Fee<N>).clone())
196            }};
197        }
198
199        // Execute the authorization.
200        let result = process!(self, logic);
201        finish!(timer, "Execute the authorization");
202        result
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use console::{
210        account::{Address, ViewKey},
211        network::Testnet3,
212        program::{Ciphertext, Value},
213        types::Field,
214    };
215    use ledger_block::Transition;
216    use ledger_store::helpers::memory::ConsensusMemory;
217
218    use indexmap::IndexMap;
219
220    type CurrentNetwork = Testnet3;
221
222    fn prepare_vm(
223        rng: &mut TestRng,
224    ) -> Result<(
225        VM<CurrentNetwork, ConsensusMemory<CurrentNetwork>>,
226        IndexMap<Field<CurrentNetwork>, Record<CurrentNetwork, Ciphertext<CurrentNetwork>>>,
227    )> {
228        // Initialize the genesis block.
229        let genesis = crate::vm::test_helpers::sample_genesis_block(rng);
230
231        // Fetch the unspent records.
232        let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::<IndexMap<_, _>>();
233
234        // Initialize the genesis block.
235        let genesis = crate::vm::test_helpers::sample_genesis_block(rng);
236
237        // Initialize the VM.
238        let vm = crate::vm::test_helpers::sample_vm();
239        // Update the VM.
240        vm.add_next_block(&genesis).unwrap();
241
242        Ok((vm, records))
243    }
244
245    #[test]
246    fn test_transfer_private_transaction_size() {
247        let rng = &mut TestRng::default();
248
249        // Initialize a new caller.
250        let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng);
251        let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap();
252        let address = Address::try_from(&caller_private_key).unwrap();
253
254        // Prepare the VM and records.
255        let (vm, records) = prepare_vm(rng).unwrap();
256
257        // Fetch the unspent record.
258        let record = records.values().next().unwrap().decrypt(&caller_view_key).unwrap();
259
260        // Prepare the inputs.
261        let inputs = [
262            Value::<CurrentNetwork>::Record(record),
263            Value::<CurrentNetwork>::from_str(&address.to_string()).unwrap(),
264            Value::<CurrentNetwork>::from_str("1u64").unwrap(),
265        ]
266        .into_iter();
267
268        // Execute.
269        let transaction =
270            vm.execute(&caller_private_key, ("credits.aleo", "transfer_private"), inputs, None, 0, None, rng).unwrap();
271
272        // Assert the size of the transaction.
273        let transaction_size_in_bytes = transaction.to_bytes_le().unwrap().len();
274        assert_eq!(3629, transaction_size_in_bytes, "Update me if serialization has changed");
275
276        // Assert the size of the execution.
277        assert!(matches!(transaction, Transaction::Execute(_, _, _)));
278        if let Transaction::Execute(_, execution, _) = &transaction {
279            let execution_size_in_bytes = execution.to_bytes_le().unwrap().len();
280            assert_eq!(2210, execution_size_in_bytes, "Update me if serialization has changed");
281        }
282    }
283
284    #[test]
285    fn test_transfer_public_transaction_size() {
286        let rng = &mut TestRng::default();
287
288        // Initialize a new caller.
289        let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng);
290        let address = Address::try_from(&caller_private_key).unwrap();
291
292        // Prepare the VM and records.
293        let (vm, _) = prepare_vm(rng).unwrap();
294
295        // Prepare the inputs.
296        let inputs = [
297            Value::<CurrentNetwork>::from_str(&address.to_string()).unwrap(),
298            Value::<CurrentNetwork>::from_str("1u64").unwrap(),
299        ]
300        .into_iter();
301
302        // Execute.
303        let transaction =
304            vm.execute(&caller_private_key, ("credits.aleo", "transfer_public"), inputs, None, 0, None, rng).unwrap();
305
306        // Assert the size of the transaction.
307        let transaction_size_in_bytes = transaction.to_bytes_le().unwrap().len();
308        assert_eq!(2807, transaction_size_in_bytes, "Update me if serialization has changed");
309
310        // Assert the size of the execution.
311        assert!(matches!(transaction, Transaction::Execute(_, _, _)));
312        if let Transaction::Execute(_, execution, _) = &transaction {
313            let execution_size_in_bytes = execution.to_bytes_le().unwrap().len();
314            assert_eq!(1388, execution_size_in_bytes, "Update me if serialization has changed");
315        }
316    }
317
318    #[test]
319    fn test_join_transaction_size() {
320        let rng = &mut TestRng::default();
321
322        // Initialize a new caller.
323        let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng);
324        let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap();
325
326        // Prepare the VM and records.
327        let (vm, records) = prepare_vm(rng).unwrap();
328
329        // Fetch the unspent records.
330        let mut records = records.values();
331        let record_1 = records.next().unwrap().decrypt(&caller_view_key).unwrap();
332        let record_2 = records.next().unwrap().decrypt(&caller_view_key).unwrap();
333
334        // Prepare the inputs.
335        let inputs = [Value::<CurrentNetwork>::Record(record_1), Value::<CurrentNetwork>::Record(record_2)].into_iter();
336
337        // Execute.
338        let transaction =
339            vm.execute(&caller_private_key, ("credits.aleo", "join"), inputs, None, 0, None, rng).unwrap();
340
341        // Assert the size of the transaction.
342        let transaction_size_in_bytes = transaction.to_bytes_le().unwrap().len();
343        assert_eq!(3474, transaction_size_in_bytes, "Update me if serialization has changed");
344
345        // Assert the size of the execution.
346        assert!(matches!(transaction, Transaction::Execute(_, _, _)));
347        if let Transaction::Execute(_, execution, _) = &transaction {
348            let execution_size_in_bytes = execution.to_bytes_le().unwrap().len();
349            assert_eq!(2055, execution_size_in_bytes, "Update me if serialization has changed");
350        }
351    }
352
353    #[test]
354    fn test_split_transaction_size() {
355        let rng = &mut TestRng::default();
356
357        // Initialize a new caller.
358        let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng);
359        let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap();
360
361        // Prepare the VM and records.
362        let (vm, records) = prepare_vm(rng).unwrap();
363
364        // Fetch the unspent record.
365        let record = records.values().next().unwrap().decrypt(&caller_view_key).unwrap();
366
367        // Prepare the inputs.
368        let inputs =
369            [Value::<CurrentNetwork>::Record(record), Value::<CurrentNetwork>::from_str("1u64").unwrap()].into_iter();
370
371        // Execute.
372        let transaction =
373            vm.execute(&caller_private_key, ("credits.aleo", "split"), inputs, None, 0, None, rng).unwrap();
374
375        // Assert the size of the transaction.
376        let transaction_size_in_bytes = transaction.to_bytes_le().unwrap().len();
377        assert_eq!(2134, transaction_size_in_bytes, "Update me if serialization has changed");
378
379        // Assert the size of the execution.
380        assert!(matches!(transaction, Transaction::Execute(_, _, _)));
381        if let Transaction::Execute(_, execution, _) = &transaction {
382            let execution_size_in_bytes = execution.to_bytes_le().unwrap().len();
383            assert_eq!(2099, execution_size_in_bytes, "Update me if serialization has changed");
384        }
385    }
386
387    #[test]
388    fn test_fee_private_transition_size() {
389        let rng = &mut TestRng::default();
390
391        // Retrieve a fee transaction.
392        let transaction = ledger_test_helpers::sample_fee_private_transaction(rng);
393        // Retrieve the fee.
394        let fee = match transaction {
395            Transaction::Fee(_, fee) => fee,
396            _ => panic!("Expected a fee transaction"),
397        };
398        // Assert the size of the transition.
399        let fee_size_in_bytes = fee.to_bytes_le().unwrap().len();
400        assert_eq!(2011, fee_size_in_bytes, "Update me if serialization has changed");
401    }
402
403    #[test]
404    fn test_fee_public_transition_size() {
405        let rng = &mut TestRng::default();
406
407        // Retrieve a fee transaction.
408        let transaction = ledger_test_helpers::sample_fee_public_transaction(rng);
409        // Retrieve the fee.
410        let fee = match transaction {
411            Transaction::Fee(_, fee) => fee,
412            _ => panic!("Expected a fee transaction"),
413        };
414        // Assert the size of the transition.
415        let fee_size_in_bytes = fee.to_bytes_le().unwrap().len();
416        assert_eq!(1384, fee_size_in_bytes, "Update me if serialization has changed");
417    }
418}