snarkvm_ledger_block/transaction/
mod.rs

1// Copyright (c) 2019-2025 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
16mod deployment;
17pub use deployment::*;
18
19mod execution;
20pub use execution::*;
21
22mod fee;
23pub use fee::*;
24
25mod bytes;
26mod merkle;
27mod serialize;
28mod string;
29
30use crate::Transition;
31use console::{
32    network::prelude::*,
33    program::{
34        Ciphertext,
35        DeploymentTree,
36        ExecutionTree,
37        ProgramOwner,
38        Record,
39        TRANSACTION_DEPTH,
40        TransactionLeaf,
41        TransactionPath,
42        TransactionTree,
43    },
44    types::{Field, Group, U64},
45};
46
47type DeploymentID<N> = Field<N>;
48type ExecutionID<N> = Field<N>;
49
50#[derive(Clone, PartialEq, Eq)]
51pub enum Transaction<N: Network> {
52    /// The deploy transaction publishes an Aleo program to the network.
53    Deploy(N::TransactionID, DeploymentID<N>, ProgramOwner<N>, Box<Deployment<N>>, Fee<N>),
54    /// The execute transaction represents a call to an Aleo program.
55    Execute(N::TransactionID, ExecutionID<N>, Box<Execution<N>>, Option<Fee<N>>),
56    /// The fee transaction represents a fee paid to the network, used for rejected transactions.
57    Fee(N::TransactionID, Fee<N>),
58}
59
60impl<N: Network> Transaction<N> {
61    /// Initializes a new deployment transaction.
62    pub fn from_deployment(owner: ProgramOwner<N>, deployment: Deployment<N>, fee: Fee<N>) -> Result<Self> {
63        // Ensure the transaction is not empty.
64        ensure!(!deployment.program().functions().is_empty(), "Attempted to create an empty deployment transaction");
65        // Compute the deployment tree.
66        let deployment_tree = Self::deployment_tree(&deployment)?;
67        // Compute the deployment ID.
68        let deployment_id = *deployment_tree.root();
69        // Compute the transaction ID
70        let transaction_id = *Self::transaction_tree(deployment_tree, Some(&fee))?.root();
71        // Ensure the owner signed the correct transaction ID.
72        ensure!(owner.verify(deployment_id), "Attempted to create a deployment transaction with an invalid owner");
73        // Ensure the owner matches the program owner in the deployment, if it exists.
74        if let Some(program_owner) = deployment.program_owner() {
75            ensure!(
76                owner.address() == program_owner,
77                "Attempted to create a deployment transaction with a provided owner '{}' and deployment owner '{}' that do not match",
78                owner.address(),
79                program_owner
80            )
81        }
82        // Construct the deployment transaction.
83        Ok(Self::Deploy(transaction_id.into(), deployment_id, owner, Box::new(deployment), fee))
84    }
85
86    /// Initializes a new execution transaction.
87    pub fn from_execution(execution: Execution<N>, fee: Option<Fee<N>>) -> Result<Self> {
88        // Ensure the transaction is not empty.
89        ensure!(!execution.is_empty(), "Attempted to create an empty execution transaction");
90        // Compute the execution tree.
91        let execution_tree = Self::execution_tree(&execution)?;
92        // Compute the execution ID.
93        let execution_id = *execution_tree.root();
94        // Compute the transaction ID.
95        let transaction_id = *Self::transaction_tree(execution_tree, fee.as_ref())?.root();
96        // Construct the execution transaction.
97        Ok(Self::Execute(transaction_id.into(), execution_id, Box::new(execution), fee))
98    }
99
100    /// Initializes a new fee transaction.
101    pub fn from_fee(fee: Fee<N>) -> Result<Self> {
102        // Ensure the fee is nonzero.
103        ensure!(!fee.is_zero()?, "Attempted to create a zero fee transaction");
104        // Compute the transaction ID.
105        let id = *Self::fee_tree(&fee)?.root();
106        // Construct the execution transaction.
107        Ok(Self::Fee(id.into(), fee))
108    }
109}
110
111impl<N: Network> Transaction<N> {
112    /// Returns `true` if the transaction is a deploy transaction.
113    #[inline]
114    pub const fn is_deploy(&self) -> bool {
115        matches!(self, Self::Deploy(..))
116    }
117
118    /// Returns `true` if the transaction is an execute transaction.
119    #[inline]
120    pub const fn is_execute(&self) -> bool {
121        matches!(self, Self::Execute(..))
122    }
123
124    /// Returns `true` if the transaction is a fee transaction.
125    #[inline]
126    pub const fn is_fee(&self) -> bool {
127        matches!(self, Self::Fee(..))
128    }
129}
130
131impl<N: Network> Transaction<N> {
132    /// Returns `true` if this transaction contains a call to `credits.aleo/split`.
133    #[inline]
134    pub fn contains_split(&self) -> bool {
135        match self {
136            // Case 1 - The transaction contains a transition that calls 'credits.aleo/split'.
137            Transaction::Execute(_, _, execution, _) => execution.transitions().any(|transition| transition.is_split()),
138            // Otherwise, return 'false'.
139            _ => false,
140        }
141    }
142
143    /// Returns `true` if this transaction contains a call to `credits.aleo/upgrade`.
144    #[inline]
145    pub fn contains_upgrade(&self) -> bool {
146        match self {
147            // Case 1 - The transaction contains a transition that calls 'credits.aleo/upgrade'.
148            Transaction::Execute(_, _, execution, _) => {
149                execution.transitions().any(|transition| transition.is_upgrade())
150            }
151            // Otherwise, return 'false'.
152            _ => false,
153        }
154    }
155}
156
157impl<N: Network> Transaction<N> {
158    /// Returns `Some(owner)` if the transaction is a deployment. Otherwise, returns `None`.
159    #[inline]
160    pub fn owner(&self) -> Option<&ProgramOwner<N>> {
161        match self {
162            Self::Deploy(_, _, owner, _, _) => Some(owner),
163            _ => None,
164        }
165    }
166
167    /// Returns `Some(deployment)` if the transaction is a deployment. Otherwise, returns `None`.
168    #[inline]
169    pub fn deployment(&self) -> Option<&Deployment<N>> {
170        match self {
171            Self::Deploy(_, _, _, deployment, _) => Some(deployment.as_ref()),
172            _ => None,
173        }
174    }
175
176    /// Returns `Some(execution)` if the transaction is an execution. Otherwise, returns `None`.
177    #[inline]
178    pub fn execution(&self) -> Option<&Execution<N>> {
179        match self {
180            Self::Execute(_, _, execution, _) => Some(execution),
181            _ => None,
182        }
183    }
184}
185
186/// A helper enum for iterators and consuming iterators over a transaction.
187enum IterWrap<T, I1: Iterator<Item = T>, I2: Iterator<Item = T>, I3: Iterator<Item = T>> {
188    Deploy(I1),
189    Execute(I2),
190    Fee(I3),
191}
192
193impl<T, I1: Iterator<Item = T>, I2: Iterator<Item = T>, I3: Iterator<Item = T>> Iterator for IterWrap<T, I1, I2, I3> {
194    type Item = T;
195
196    fn next(&mut self) -> Option<Self::Item> {
197        match self {
198            Self::Deploy(iter) => iter.next(),
199            Self::Execute(iter) => iter.next(),
200            Self::Fee(iter) => iter.next(),
201        }
202    }
203}
204
205impl<T, I1: DoubleEndedIterator<Item = T>, I2: DoubleEndedIterator<Item = T>, I3: DoubleEndedIterator<Item = T>>
206    DoubleEndedIterator for IterWrap<T, I1, I2, I3>
207{
208    fn next_back(&mut self) -> Option<Self::Item> {
209        match self {
210            Self::Deploy(iter) => iter.next_back(),
211            Self::Execute(iter) => iter.next_back(),
212            Self::Fee(iter) => iter.next_back(),
213        }
214    }
215}
216
217impl<N: Network> Transaction<N> {
218    /// Returns the transaction ID.
219    pub const fn id(&self) -> N::TransactionID {
220        match self {
221            Self::Deploy(id, ..) => *id,
222            Self::Execute(id, ..) => *id,
223            Self::Fee(id, ..) => *id,
224        }
225    }
226
227    /// Returns the transaction total fee.
228    pub fn fee_amount(&self) -> Result<U64<N>> {
229        match self {
230            Self::Deploy(_, _, _, _, fee) => fee.amount(),
231            Self::Execute(_, _, _, Some(fee)) => fee.amount(),
232            Self::Execute(_, _, _, None) => Ok(U64::zero()),
233            Self::Fee(_, fee) => fee.amount(),
234        }
235    }
236
237    /// Returns the transaction base fee.
238    pub fn base_fee_amount(&self) -> Result<U64<N>> {
239        match self {
240            Self::Deploy(_, _, _, _, fee) => fee.base_amount(),
241            Self::Execute(_, _, _, Some(fee)) => fee.base_amount(),
242            Self::Execute(_, _, _, None) => Ok(U64::zero()),
243            Self::Fee(_, fee) => fee.base_amount(),
244        }
245    }
246
247    /// Returns the transaction priority fee.
248    pub fn priority_fee_amount(&self) -> Result<U64<N>> {
249        match self {
250            Self::Deploy(_, _, _, _, fee) => fee.priority_amount(),
251            Self::Execute(_, _, _, Some(fee)) => fee.priority_amount(),
252            Self::Execute(_, _, _, None) => Ok(U64::zero()),
253            Self::Fee(_, fee) => fee.priority_amount(),
254        }
255    }
256
257    /// Returns the fee transition.
258    pub fn fee_transition(&self) -> Option<Fee<N>> {
259        match self {
260            Self::Deploy(_, _, _, _, fee) => Some(fee.clone()),
261            Self::Execute(_, _, _, fee) => fee.clone(),
262            Self::Fee(_, fee) => Some(fee.clone()),
263        }
264    }
265}
266
267impl<N: Network> Transaction<N> {
268    /// Returns `true` if the transaction contains the given transition ID.
269    pub fn contains_transition(&self, transition_id: &N::TransitionID) -> bool {
270        match self {
271            // Check the fee.
272            Self::Deploy(_, _, _, _, fee) => fee.id() == transition_id,
273            // Check the execution and fee.
274            Self::Execute(_, _, execution, fee) => {
275                execution.contains_transition(transition_id)
276                    || fee.as_ref().is_some_and(|fee| fee.id() == transition_id)
277            }
278            // Check the fee.
279            Self::Fee(_, fee) => fee.id() == transition_id,
280        }
281    }
282
283    /// Returns `true` if the transaction contains the given serial number.
284    pub fn contains_serial_number(&self, serial_number: &Field<N>) -> bool {
285        self.transitions().any(|transition| transition.contains_serial_number(serial_number))
286    }
287
288    /// Returns `true` if the transaction contains the given commitment.
289    pub fn contains_commitment(&self, commitment: &Field<N>) -> bool {
290        self.transitions().any(|transition| transition.contains_commitment(commitment))
291    }
292}
293
294impl<N: Network> Transaction<N> {
295    /// Returns the transition with the corresponding transition ID, if it exists.
296    pub fn find_transition(&self, transition_id: &N::TransitionID) -> Option<&Transition<N>> {
297        match self {
298            // Check the fee.
299            Self::Deploy(_, _, _, _, fee) => match fee.id() == transition_id {
300                true => Some(fee.transition()),
301                false => None,
302            },
303            // Check the execution and fee.
304            Self::Execute(_, _, execution, fee) => execution.get_transition(transition_id).or_else(|| {
305                fee.as_ref().and_then(|fee| match fee.id() == transition_id {
306                    true => Some(fee.transition()),
307                    false => None,
308                })
309            }),
310            // Check the fee.
311            Self::Fee(_, fee) => match fee.id() == transition_id {
312                true => Some(fee.transition()),
313                false => None,
314            },
315        }
316    }
317
318    /// Returns the transition for the given serial number, if it exists.
319    pub fn find_transition_for_serial_number(&self, serial_number: &Field<N>) -> Option<&Transition<N>> {
320        self.transitions().find(|transition| transition.contains_serial_number(serial_number))
321    }
322
323    /// Returns the transition for the given commitment, if it exists.
324    pub fn find_transition_for_commitment(&self, commitment: &Field<N>) -> Option<&Transition<N>> {
325        self.transitions().find(|transition| transition.contains_commitment(commitment))
326    }
327
328    /// Returns the record with the corresponding commitment, if it exists.
329    pub fn find_record(&self, commitment: &Field<N>) -> Option<&Record<N, Ciphertext<N>>> {
330        self.transitions().find_map(|transition| transition.find_record(commitment))
331    }
332}
333
334impl<N: Network> Transaction<N> {
335    /// Returns an iterator over the transition IDs, for all transitions.
336    pub fn transition_ids(&self) -> impl '_ + DoubleEndedIterator<Item = &N::TransitionID> {
337        self.transitions().map(Transition::id)
338    }
339
340    /// Returns an iterator over all transitions.
341    pub fn transitions(&self) -> impl '_ + DoubleEndedIterator<Item = &Transition<N>> {
342        match self {
343            Self::Deploy(_, _, _, _, fee) => IterWrap::Deploy(Some(fee.transition()).into_iter()),
344            Self::Execute(_, _, execution, fee) => {
345                IterWrap::Execute(execution.transitions().chain(fee.as_ref().map(|fee| fee.transition())))
346            }
347            Self::Fee(_, fee) => IterWrap::Fee(Some(fee.transition()).into_iter()),
348        }
349    }
350
351    /* Input */
352
353    /// Returns an iterator over the input IDs, for all transition inputs that are records.
354    pub fn input_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> {
355        self.transitions().flat_map(Transition::input_ids)
356    }
357
358    /// Returns an iterator over the serial numbers, for all transition inputs that are records.
359    pub fn serial_numbers(&self) -> impl '_ + Iterator<Item = &Field<N>> {
360        self.transitions().flat_map(Transition::serial_numbers)
361    }
362
363    /// Returns an iterator over the tags, for all transition inputs that are records.
364    pub fn tags(&self) -> impl '_ + Iterator<Item = &Field<N>> {
365        self.transitions().flat_map(Transition::tags)
366    }
367
368    /* Output */
369
370    /// Returns an iterator over the output IDs, for all transition inputs that are records.
371    pub fn output_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> {
372        self.transitions().flat_map(Transition::output_ids)
373    }
374
375    /// Returns an iterator over the commitments, for all transition outputs that are records.
376    pub fn commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> {
377        self.transitions().flat_map(Transition::commitments)
378    }
379
380    /// Returns an iterator over the records, for all transition outputs that are records.
381    pub fn records(&self) -> impl '_ + Iterator<Item = (&Field<N>, &Record<N, Ciphertext<N>>)> {
382        self.transitions().flat_map(Transition::records)
383    }
384
385    /// Returns an iterator over the nonces, for all transition outputs that are records.
386    pub fn nonces(&self) -> impl '_ + Iterator<Item = &Group<N>> {
387        self.transitions().flat_map(Transition::nonces)
388    }
389
390    /// Returns an iterator over the transition public keys, for all transitions.
391    pub fn transition_public_keys(&self) -> impl '_ + DoubleEndedIterator<Item = &Group<N>> {
392        self.transitions().map(Transition::tpk)
393    }
394
395    /// Returns an iterator over the transition commitments, for all transitions.
396    pub fn transition_commitments(&self) -> impl '_ + DoubleEndedIterator<Item = &Field<N>> {
397        self.transitions().map(Transition::tcm)
398    }
399}
400
401impl<N: Network> Transaction<N> {
402    /// Returns a consuming iterator over the transition IDs, for all transitions.
403    pub fn into_transition_ids(self) -> impl Iterator<Item = N::TransitionID> {
404        self.into_transitions().map(Transition::into_id)
405    }
406
407    /// Returns a consuming iterator over all transitions.
408    pub fn into_transitions(self) -> impl DoubleEndedIterator<Item = Transition<N>> {
409        match self {
410            Self::Deploy(_, _, _, _, fee) => IterWrap::Deploy(Some(fee.into_transition()).into_iter()),
411            Self::Execute(_, _, execution, fee) => {
412                IterWrap::Execute(execution.into_transitions().chain(fee.map(|fee| fee.into_transition())))
413            }
414            Self::Fee(_, fee) => IterWrap::Fee(Some(fee.into_transition()).into_iter()),
415        }
416    }
417
418    /// Returns a consuming iterator over the transition public keys, for all transitions.
419    pub fn into_transition_public_keys(self) -> impl DoubleEndedIterator<Item = Group<N>> {
420        self.into_transitions().map(Transition::into_tpk)
421    }
422
423    /// Returns a consuming iterator over the tags, for all transition inputs that are records.
424    pub fn into_tags(self) -> impl Iterator<Item = Field<N>> {
425        self.into_transitions().flat_map(Transition::into_tags)
426    }
427
428    /// Returns a consuming iterator over the serial numbers, for all transition inputs that are records.
429    pub fn into_serial_numbers(self) -> impl Iterator<Item = Field<N>> {
430        self.into_transitions().flat_map(Transition::into_serial_numbers)
431    }
432
433    /// Returns a consuming iterator over the commitments, for all transition outputs that are records.
434    pub fn into_commitments(self) -> impl Iterator<Item = Field<N>> {
435        self.into_transitions().flat_map(Transition::into_commitments)
436    }
437
438    /// Returns a consuming iterator over the records, for all transition outputs that are records.
439    pub fn into_records(self) -> impl Iterator<Item = (Field<N>, Record<N, Ciphertext<N>>)> {
440        self.into_transitions().flat_map(Transition::into_records)
441    }
442
443    /// Returns a consuming iterator over the nonces, for all transition outputs that are records.
444    pub fn into_nonces(self) -> impl Iterator<Item = Group<N>> {
445        self.into_transitions().flat_map(Transition::into_nonces)
446    }
447}
448
449#[cfg(test)]
450pub mod test_helpers {
451    use super::*;
452    use console::{account::PrivateKey, network::MainnetV0, program::ProgramOwner, types::Address};
453
454    type CurrentNetwork = MainnetV0;
455
456    /// Samples a random deployment transaction with a private or public fee.
457    pub fn sample_deployment_transaction(
458        version: u8,
459        edition: u16,
460        is_fee_private: bool,
461        rng: &mut TestRng,
462    ) -> Transaction<CurrentNetwork> {
463        // Sample a private key.
464        let private_key = PrivateKey::new(rng).unwrap();
465        // Sample a deployment.
466        let deployment = match version {
467            1 => crate::transaction::deployment::test_helpers::sample_deployment_v1(edition, rng),
468            2 => {
469                let mut deployment = crate::transaction::deployment::test_helpers::sample_deployment_v2(edition, rng);
470                // Set the program checksum.
471                deployment.set_program_checksum_raw(Some(deployment.program().to_checksum()));
472                // Set the program owner to the address of the private key.
473                deployment.set_program_owner_raw(Some(Address::try_from(&private_key).unwrap()));
474                // Return the deployment.
475                deployment
476            }
477            _ => panic!("Invalid deployment version."),
478        };
479
480        // Compute the deployment ID.
481        let deployment_id = deployment.to_deployment_id().unwrap();
482        // Construct a program owner.
483        let owner = ProgramOwner::new(&private_key, deployment_id, rng).unwrap();
484
485        // Sample the fee.
486        let fee = match is_fee_private {
487            true => crate::transaction::fee::test_helpers::sample_fee_private(deployment_id, rng),
488            false => crate::transaction::fee::test_helpers::sample_fee_public(deployment_id, rng),
489        };
490
491        // Construct a deployment transaction.
492        Transaction::from_deployment(owner, deployment, fee).unwrap()
493    }
494
495    /// Samples a random execution transaction with a private or public fee.
496    pub fn sample_execution_transaction_with_fee(
497        is_fee_private: bool,
498        rng: &mut TestRng,
499        index: usize,
500    ) -> Transaction<CurrentNetwork> {
501        // Sample an execution.
502        let execution = crate::transaction::execution::test_helpers::sample_execution(rng, index);
503        // Compute the execution ID.
504        let execution_id = execution.to_execution_id().unwrap();
505
506        // Sample the fee.
507        let fee = match is_fee_private {
508            true => crate::transaction::fee::test_helpers::sample_fee_private(execution_id, rng),
509            false => crate::transaction::fee::test_helpers::sample_fee_public(execution_id, rng),
510        };
511
512        // Construct an execution transaction.
513        Transaction::from_execution(execution, Some(fee)).unwrap()
514    }
515
516    /// Samples a random fee transaction.
517    pub fn sample_private_fee_transaction(rng: &mut TestRng) -> Transaction<CurrentNetwork> {
518        // Sample a fee.
519        let fee = crate::transaction::fee::test_helpers::sample_fee_private_hardcoded(rng);
520        // Construct a fee transaction.
521        Transaction::from_fee(fee).unwrap()
522    }
523
524    /// Samples a random fee transaction.
525    pub fn sample_fee_public_transaction(rng: &mut TestRng) -> Transaction<CurrentNetwork> {
526        // Sample a fee.
527        let fee = crate::transaction::fee::test_helpers::sample_fee_public_hardcoded(rng);
528        // Construct a fee transaction.
529        Transaction::from_fee(fee).unwrap()
530    }
531}
532
533#[cfg(test)]
534mod tests {
535    use super::*;
536
537    #[test]
538    fn test_transaction_id() -> Result<()> {
539        let rng = &mut TestRng::default();
540
541        // Transaction IDs are created using `transaction_tree`.
542        for expected in [
543            crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng),
544            crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng),
545            crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng),
546            crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng),
547            crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng),
548            crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng),
549            crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng),
550            crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng),
551            crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0),
552            crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng, 0),
553        ]
554        .into_iter()
555        {
556            match expected {
557                // Compare against transaction IDs created using `deployment_tree`.
558                Transaction::Deploy(transaction_id, deployment_id, _, ref deployment, _) => {
559                    let expected_transaction_id = *expected.clone().to_tree()?.root();
560                    assert_eq!(expected_transaction_id, *transaction_id);
561                    let expected_deployment_id = *Transaction::deployment_tree(deployment)?.root();
562                    assert_eq!(expected_deployment_id, deployment_id);
563                }
564                // Compare against transaction IDs created using `execution_tree`.
565                Transaction::Execute(transaction_id, execution_id, ref execution, _) => {
566                    let expected_transaction_id = *expected.clone().to_tree()?.root();
567                    assert_eq!(expected_transaction_id, *transaction_id);
568                    let expected_execution_id = *Transaction::execution_tree(execution)?.root();
569                    assert_eq!(expected_execution_id, execution_id);
570                }
571                _ => panic!("Unexpected test case."),
572            };
573        }
574
575        Ok(())
576    }
577}