/ ledger / block / src / transactions / 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  pub mod confirmed;
 17  pub use confirmed::*;
 18  
 19  pub mod rejected;
 20  pub use rejected::*;
 21  
 22  mod bytes;
 23  mod merkle;
 24  mod serialize;
 25  mod string;
 26  
 27  use crate::{Transaction, Transition};
 28  use alphavm_ledger_committee::Committee;
 29  use alphavm_ledger_narwhal_batch_header::BatchHeader;
 30  use alphavm_synthesizer_program::FinalizeOperation;
 31  use console::{
 32      network::prelude::*,
 33      program::{
 34          Ciphertext,
 35          FINALIZE_ID_DEPTH,
 36          FINALIZE_OPERATIONS_DEPTH,
 37          ProgramOwner,
 38          Record,
 39          TRANSACTIONS_DEPTH,
 40          TransactionsPath,
 41          TransactionsTree,
 42      },
 43      types::{Field, Group, U64},
 44  };
 45  
 46  use indexmap::IndexMap;
 47  
 48  #[cfg(not(feature = "serial"))]
 49  use rayon::prelude::*;
 50  
 51  /// The set of transactions included in a block.
 52  #[derive(Clone, PartialEq, Eq)]
 53  pub struct Transactions<N: Network> {
 54      /// The transactions included in a block.
 55      transactions: IndexMap<N::TransactionID, ConfirmedTransaction<N>>,
 56  }
 57  
 58  impl<N: Network> Transactions<N> {
 59      /// Initializes from a given transactions list.
 60      pub fn from(transactions: &[ConfirmedTransaction<N>]) -> Self {
 61          Self::from_iter(transactions.iter())
 62      }
 63  }
 64  
 65  impl<N: Network> FromIterator<ConfirmedTransaction<N>> for Transactions<N> {
 66      /// Initializes from an iterator of transactions.
 67      fn from_iter<T: IntoIterator<Item = ConfirmedTransaction<N>>>(iter: T) -> Self {
 68          Self { transactions: iter.into_iter().map(|transaction| (transaction.id(), transaction)).collect() }
 69      }
 70  }
 71  
 72  impl<'a, N: Network> FromIterator<&'a ConfirmedTransaction<N>> for Transactions<N> {
 73      /// Initializes from an iterator of transactions.
 74      fn from_iter<T: IntoIterator<Item = &'a ConfirmedTransaction<N>>>(iter: T) -> Self {
 75          Self::from_iter(iter.into_iter().cloned())
 76      }
 77  }
 78  
 79  impl<N: Network> Transactions<N> {
 80      /// Returns the transaction for the given transaction ID.
 81      pub fn get(&self, transaction_id: &N::TransactionID) -> Option<&ConfirmedTransaction<N>> {
 82          self.transactions.get(transaction_id)
 83      }
 84  
 85      /// Returns 'true' if there are no accepted or rejected transactions.
 86      pub fn is_empty(&self) -> bool {
 87          self.transactions.is_empty()
 88      }
 89  
 90      /// Returns the number of confirmed transactions.
 91      pub fn len(&self) -> usize {
 92          self.transactions.len()
 93      }
 94  
 95      /// Returns the number of accepted transactions.
 96      pub fn num_accepted(&self) -> usize {
 97          cfg_values!(self.transactions).filter(|tx| tx.is_accepted()).count()
 98      }
 99  
100      /// Returns the number of rejected transactions.
101      pub fn num_rejected(&self) -> usize {
102          cfg_values!(self.transactions).filter(|tx| tx.is_rejected()).count()
103      }
104  
105      /// Returns the number of finalize operations.
106      pub fn num_finalize(&self) -> usize {
107          cfg_values!(self.transactions).map(|tx| tx.num_finalize()).sum()
108      }
109  
110      /// Returns the index of the transaction with the given ID, if it exists.
111      pub fn index_of(&self, transaction_id: &N::TransactionID) -> Option<usize> {
112          self.transactions.get_index_of(transaction_id)
113      }
114  }
115  
116  impl<N: Network> Transactions<N> {
117      /// Returns `true` if the transactions contains the given transition ID.
118      pub fn contains_transition(&self, transition_id: &N::TransitionID) -> bool {
119          cfg_values!(self.transactions).any(|tx| tx.contains_transition(transition_id))
120      }
121  
122      /// Returns `true` if the transactions contains the given serial number.
123      pub fn contains_serial_number(&self, serial_number: &Field<N>) -> bool {
124          cfg_values!(self.transactions).any(|tx| tx.contains_serial_number(serial_number))
125      }
126  
127      /// Returns `true` if the transactions contains the given commitment.
128      pub fn contains_commitment(&self, commitment: &Field<N>) -> bool {
129          cfg_values!(self.transactions).any(|tx| tx.contains_commitment(commitment))
130      }
131  }
132  
133  impl<N: Network> Transactions<N> {
134      /// Returns the confirmed transaction for the given unconfirmed transaction ID, if it exists.
135      pub fn find_confirmed_transaction_for_unconfirmed_transaction_id(
136          &self,
137          unconfirmed_transaction_id: &N::TransactionID,
138      ) -> Option<&ConfirmedTransaction<N>> {
139          cfg_find!(self.transactions, |txn| txn.contains_unconfirmed_transaction_id(unconfirmed_transaction_id))
140      }
141  
142      /// Returns the transaction with the given transition ID, if it exists.
143      ///
144      /// If the given transition ID is a fee transition for a rejected transaction,
145      /// this will return the fee transaction.
146      pub fn find_transaction_for_transition_id(&self, transition_id: &N::TransitionID) -> Option<&Transaction<N>> {
147          cfg_find!(self.transactions, |txn| txn.contains_transition(transition_id)).map(|tx| tx.transaction())
148      }
149  
150      /// Returns the unconfirmed transaction with the given transition ID, if it exists.
151      ///
152      /// If the given transition ID is a fee transition for a rejected transaction,
153      /// this will return the original/unconfirmed transaction, not the fee transaction.
154      pub fn find_unconfirmed_transaction_for_transition_id(
155          &self,
156          transition_id: &N::TransitionID,
157      ) -> Result<Option<Transaction<N>>> {
158          let result = cfg_find!(self.transactions, |tx| tx.contains_transition(transition_id));
159  
160          match result {
161              Some(txn) => Ok(Some(txn.to_unconfirmed_transaction()?)),
162              None => Ok(None),
163          }
164      }
165  
166      /// Returns the transaction with the given serial number, if it exists.
167      pub fn find_transaction_for_serial_number(&self, serial_number: &Field<N>) -> Option<&Transaction<N>> {
168          cfg_find!(self.transactions, |txn| txn.contains_serial_number(serial_number)).map(|tx| tx.transaction())
169      }
170  
171      /// Returns the transaction with the given commitment, if it exists.
172      pub fn find_transaction_for_commitment(&self, commitment: &Field<N>) -> Option<&Transaction<N>> {
173          cfg_find!(self.transactions, |txn| txn.contains_commitment(commitment)).map(|tx| tx.transaction())
174      }
175  
176      /// Returns the transition with the corresponding transition ID, if it exists.
177      pub fn find_transition(&self, transition_id: &N::TransitionID) -> Option<&Transition<N>> {
178          cfg_find_map!(self.transactions, |txn| txn.find_transition(transition_id))
179      }
180  
181      /// Returns the transition for the given serial number, if it exists.
182      pub fn find_transition_for_serial_number(&self, serial_number: &Field<N>) -> Option<&Transition<N>> {
183          cfg_find_map!(self.transactions, |txn| txn.find_transition_for_serial_number(serial_number))
184      }
185  
186      /// Returns the transition for the given commitment, if it exists.
187      pub fn find_transition_for_commitment(&self, commitment: &Field<N>) -> Option<&Transition<N>> {
188          cfg_find_map!(self.transactions, |txn| txn.find_transition_for_commitment(commitment))
189      }
190  
191      /// Returns the record with the corresponding commitment, if it exists.
192      pub fn find_record(&self, commitment: &Field<N>) -> Option<&Record<N, Ciphertext<N>>> {
193          cfg_find_map!(self.transactions, |txn| txn.find_record(commitment))
194      }
195  }
196  
197  impl<N: Network> Transactions<N> {
198      /// The maximum number of transactions allowed in a block.
199      pub const MAX_TRANSACTIONS: usize = usize::pow(2, TRANSACTIONS_DEPTH as u32).saturating_sub(1);
200  
201      /// The maximum number of aborted transactions allowed in a block.
202      pub fn max_aborted_transactions() -> Result<usize> {
203          Ok(BatchHeader::<N>::MAX_TRANSMISSIONS_PER_BATCH
204              * BatchHeader::<N>::MAX_GC_ROUNDS
205              * Committee::<N>::max_committee_size()? as usize)
206      }
207  
208      /// Returns an iterator over all transactions, for all transactions in `self`.
209      pub fn iter(&self) -> impl '_ + ExactSizeIterator<Item = &ConfirmedTransaction<N>> {
210          self.transactions.values()
211      }
212  
213      /// Returns a parallel iterator over all transactions, for all transactions in `self`.
214      #[cfg(not(feature = "serial"))]
215      pub fn par_iter(&self) -> impl '_ + IndexedParallelIterator<Item = &ConfirmedTransaction<N>> {
216          self.transactions.par_values()
217      }
218  
219      /// Returns an iterator over the transaction IDs, for all transactions in `self`.
220      pub fn transaction_ids(&self) -> impl '_ + ExactSizeIterator<Item = &N::TransactionID> {
221          self.transactions.keys()
222      }
223  
224      /// Returns an iterator over all transactions in `self` that are accepted deploy transactions.
225      pub fn deployments(&self) -> impl '_ + Iterator<Item = &ConfirmedTransaction<N>> {
226          self.iter().filter(|tx| tx.is_accepted() && tx.is_deploy())
227      }
228  
229      /// Returns an iterator over all transactions in `self` that are accepted execute transactions.
230      pub fn executions(&self) -> impl '_ + Iterator<Item = &ConfirmedTransaction<N>> {
231          self.iter().filter(|tx| tx.is_accepted() && tx.is_execute())
232      }
233  
234      /// Returns an iterator over all transitions.
235      pub fn transitions(&self) -> impl '_ + Iterator<Item = &Transition<N>> {
236          self.iter().flat_map(|tx| tx.transitions())
237      }
238  
239      /// Returns an iterator over the transition IDs, for all transitions.
240      pub fn transition_ids(&self) -> impl '_ + Iterator<Item = &N::TransitionID> {
241          self.iter().flat_map(|tx| tx.transition_ids())
242      }
243  
244      /// Returns an iterator over the transition public keys, for all transactions.
245      pub fn transition_public_keys(&self) -> impl '_ + Iterator<Item = &Group<N>> {
246          self.iter().flat_map(|tx| tx.transition_public_keys())
247      }
248  
249      /// Returns an iterator over the transition commitments, for all transactions.
250      pub fn transition_commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> {
251          self.iter().flat_map(|tx| tx.transition_commitments())
252      }
253  
254      /// Returns an iterator over the tags, for all transition inputs that are records.
255      pub fn tags(&self) -> impl '_ + Iterator<Item = &Field<N>> {
256          self.iter().flat_map(|tx| tx.tags())
257      }
258  
259      /// Returns an iterator over the input IDs, for all transition inputs that are records.
260      pub fn input_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> {
261          self.iter().flat_map(|tx| tx.input_ids())
262      }
263  
264      /// Returns an iterator over the serial numbers, for all transition inputs that are records.
265      pub fn serial_numbers(&self) -> impl '_ + Iterator<Item = &Field<N>> {
266          self.iter().flat_map(|tx| tx.serial_numbers())
267      }
268  
269      /// Returns an iterator over the output IDs, for all transition inputs that are records.
270      pub fn output_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> {
271          self.iter().flat_map(|tx| tx.output_ids())
272      }
273  
274      /// Returns an iterator over the commitments, for all transition outputs that are records.
275      pub fn commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> {
276          self.iter().flat_map(|tx| tx.commitments())
277      }
278  
279      /// Returns an iterator over the records, for all transition outputs that are records.
280      pub fn records(&self) -> impl '_ + Iterator<Item = (&Field<N>, &Record<N, Ciphertext<N>>)> {
281          self.iter().flat_map(|tx| tx.records())
282      }
283  
284      /// Returns an iterator over the nonces, for all transition outputs that are records.
285      pub fn nonces(&self) -> impl '_ + Iterator<Item = &Group<N>> {
286          self.iter().flat_map(|tx| tx.nonces())
287      }
288  
289      /// Returns an iterator over the transaction fee amounts, for all transactions.
290      pub fn transaction_fee_amounts(&self) -> impl '_ + Iterator<Item = Result<U64<N>>> {
291          self.iter().map(|tx| tx.fee_amount())
292      }
293  
294      /// Returns an iterator over the finalize operations, for all transactions.
295      pub fn finalize_operations(&self) -> impl '_ + Iterator<Item = &FinalizeOperation<N>> {
296          self.iter().flat_map(|tx| tx.finalize_operations())
297      }
298  }
299  
300  impl<N: Network> IntoIterator for Transactions<N> {
301      type IntoIter = indexmap::map::IntoValues<N::TransactionID, Self::Item>;
302      type Item = ConfirmedTransaction<N>;
303  
304      /// Returns a consuming iterator over all transactions, for all transactions in `self`.
305      fn into_iter(self) -> Self::IntoIter {
306          self.transactions.into_values()
307      }
308  }
309  
310  impl<N: Network> Transactions<N> {
311      /// Returns a consuming iterator over the transaction IDs, for all transactions in `self`.
312      pub fn into_transaction_ids(self) -> impl ExactSizeIterator<Item = N::TransactionID> {
313          self.transactions.into_keys()
314      }
315  
316      /// Returns a consuming iterator over all transactions in `self` that are accepted deploy transactions.
317      pub fn into_deployments(self) -> impl Iterator<Item = ConfirmedTransaction<N>> {
318          self.into_iter().filter(|tx| tx.is_accepted() && tx.is_deploy())
319      }
320  
321      /// Returns a consuming iterator over all transactions in `self` that are accepted execute transactions.
322      pub fn into_executions(self) -> impl Iterator<Item = ConfirmedTransaction<N>> {
323          self.into_iter().filter(|tx| tx.is_accepted() && tx.is_execute())
324      }
325  
326      /// Returns a consuming iterator over all transitions.
327      pub fn into_transitions(self) -> impl Iterator<Item = Transition<N>> {
328          self.into_iter().flat_map(|tx| tx.into_transaction().into_transitions())
329      }
330  
331      /// Returns a consuming iterator over the transition IDs, for all transitions.
332      pub fn into_transition_ids(self) -> impl Iterator<Item = N::TransitionID> {
333          self.into_iter().flat_map(|tx| tx.into_transaction().into_transition_ids())
334      }
335  
336      /// Returns a consuming iterator over the transition public keys, for all transactions.
337      pub fn into_transition_public_keys(self) -> impl Iterator<Item = Group<N>> {
338          self.into_iter().flat_map(|tx| tx.into_transaction().into_transition_public_keys())
339      }
340  
341      /// Returns a consuming iterator over the tags, for all transition inputs that are records.
342      pub fn into_tags(self) -> impl Iterator<Item = Field<N>> {
343          self.into_iter().flat_map(|tx| tx.into_transaction().into_tags())
344      }
345  
346      /// Returns a consuming iterator over the serial numbers, for all transition inputs that are records.
347      pub fn into_serial_numbers(self) -> impl Iterator<Item = Field<N>> {
348          self.into_iter().flat_map(|tx| tx.into_transaction().into_serial_numbers())
349      }
350  
351      /// Returns a consuming iterator over the commitments, for all transition outputs that are records.
352      pub fn into_commitments(self) -> impl Iterator<Item = Field<N>> {
353          self.into_iter().flat_map(|tx| tx.into_transaction().into_commitments())
354      }
355  
356      /// Returns a consuming iterator over the records, for all transition outputs that are records.
357      pub fn into_records(self) -> impl Iterator<Item = (Field<N>, Record<N, Ciphertext<N>>)> {
358          self.into_iter().flat_map(|tx| tx.into_transaction().into_records())
359      }
360  
361      /// Returns a consuming iterator over the nonces, for all transition outputs that are records.
362      pub fn into_nonces(self) -> impl Iterator<Item = Group<N>> {
363          self.into_iter().flat_map(|tx| tx.into_transaction().into_nonces())
364      }
365  }
366  
367  #[cfg(test)]
368  pub mod test_helpers {
369      use super::*;
370  
371      type CurrentNetwork = console::network::MainnetV0;
372  
373      /// Samples a block transactions.
374      pub(crate) fn sample_block_transactions(rng: &mut TestRng) -> Transactions<CurrentNetwork> {
375          crate::test_helpers::sample_genesis_block(rng).transactions().clone()
376      }
377  }
378  
379  #[cfg(test)]
380  mod tests {
381      use super::*;
382      use alphavm_ledger_narwhal_batch_header::BatchHeader;
383  
384      type CurrentNetwork = console::network::MainnetV0;
385  
386      #[test]
387      fn test_max_transmissions() {
388          // Determine the maximum number of transmissions in a block.
389          let max_transmissions_per_block = BatchHeader::<CurrentNetwork>::MAX_TRANSMISSIONS_PER_BATCH
390              * BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS
391              * CurrentNetwork::LATEST_MAX_CERTIFICATES().unwrap() as usize;
392  
393          // Note: The maximum number of *transmissions* in a block cannot exceed the maximum number of *transactions* in a block.
394          // If you intended to change the number of 'MAX_TRANSACTIONS', note that this will break the inclusion proof,
395          // and you will need to migrate all users to a new circuit for the inclusion proof.
396          assert!(
397              max_transmissions_per_block <= Transactions::<CurrentNetwork>::MAX_TRANSACTIONS,
398              "The maximum number of transmissions in a block is too large"
399          );
400      }
401  }