snarkvm_ledger_block/transactions/
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
16pub mod confirmed;
17pub use confirmed::*;
18
19pub mod rejected;
20pub use rejected::*;
21
22mod bytes;
23mod merkle;
24mod serialize;
25mod string;
26
27use crate::{Transaction, Transition};
28use console::{
29    network::prelude::*,
30    program::{
31        Ciphertext,
32        FINALIZE_ID_DEPTH,
33        FINALIZE_OPERATIONS_DEPTH,
34        ProgramOwner,
35        Record,
36        TRANSACTIONS_DEPTH,
37        TransactionsPath,
38        TransactionsTree,
39    },
40    types::{Field, Group, U64},
41};
42use snarkvm_ledger_committee::Committee;
43use snarkvm_ledger_narwhal_batch_header::BatchHeader;
44use snarkvm_synthesizer_program::FinalizeOperation;
45
46use indexmap::IndexMap;
47
48#[cfg(not(feature = "serial"))]
49use rayon::prelude::*;
50
51/// The set of transactions included in a block.
52#[derive(Clone, PartialEq, Eq)]
53pub struct Transactions<N: Network> {
54    /// The transactions included in a block.
55    transactions: IndexMap<N::TransactionID, ConfirmedTransaction<N>>,
56}
57
58impl<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
65impl<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
72impl<'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
79impl<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
116impl<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
133impl<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
197impl<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
300impl<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
310impl<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)]
368pub 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)]
380mod tests {
381    use super::*;
382    use snarkvm_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}