/ ledger / block / src / transaction / mod.rs
mod.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the alphavm 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  mod deployment;
 17  pub use deployment::*;
 18  
 19  mod execution;
 20  pub use execution::*;
 21  
 22  mod fee;
 23  pub use fee::*;
 24  
 25  mod bytes;
 26  mod merkle;
 27  mod serialize;
 28  mod string;
 29  
 30  use crate::Transition;
 31  use 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  
 47  type DeploymentID<N> = Field<N>;
 48  type ExecutionID<N> = Field<N>;
 49  
 50  #[derive(Clone, PartialEq, Eq)]
 51  pub enum Transaction<N: Network> {
 52      /// The deploy transaction publishes an Alpha 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 Alpha 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  
 60  impl<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  
111  impl<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  
131  impl<N: Network> Transaction<N> {
132      /// Returns `true` if this transaction contains a call to `credits.alpha/split`.
133      #[inline]
134      pub fn contains_split(&self) -> bool {
135          match self {
136              // Case 1 - The transaction contains a transition that calls 'credits.alpha/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.alpha/upgrade`.
144      #[inline]
145      pub fn contains_upgrade(&self) -> bool {
146          match self {
147              // Case 1 - The transaction contains a transition that calls 'credits.alpha/upgrade'.
148              Transaction::Execute(_, _, execution, _) => {
149                  execution.transitions().any(|transition| transition.is_upgrade())
150              }
151              // Otherwise, return 'false'.
152              _ => false,
153          }
154      }
155  }
156  
157  impl<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.
187  enum 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  
193  impl<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  
205  impl<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  
217  impl<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  
267  impl<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  
294  impl<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  
334  impl<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  
401  impl<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)]
450  pub 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)]
534  mod 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  }