Skip to main content

snarkvm_ledger_block/transactions/
mod.rs

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