snarkvm_ledger_store/transition/
input.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
16use crate::{
17    atomic_batch_scope,
18    helpers::{Map, MapRead},
19};
20use console::{
21    network::prelude::*,
22    program::{Ciphertext, Plaintext},
23    types::Field,
24};
25use ledger_block::Input;
26
27use aleo_std_storage::StorageMode;
28use anyhow::Result;
29use std::borrow::Cow;
30
31/// A trait for transition input storage.
32pub trait InputStorage<N: Network>: Clone + Send + Sync {
33    /// The mapping of `transition ID` to `input IDs`.
34    type IDMap: for<'a> Map<'a, N::TransitionID, Vec<Field<N>>>;
35    /// The mapping of `input ID` to `transition ID`.
36    type ReverseIDMap: for<'a> Map<'a, Field<N>, N::TransitionID>;
37    /// The mapping of `plaintext hash` to `(optional) plaintext`.
38    type ConstantMap: for<'a> Map<'a, Field<N>, Option<Plaintext<N>>>;
39    /// The mapping of `plaintext hash` to `(optional) plaintext`.
40    type PublicMap: for<'a> Map<'a, Field<N>, Option<Plaintext<N>>>;
41    /// The mapping of `ciphertext hash` to `(optional) ciphertext`.
42    type PrivateMap: for<'a> Map<'a, Field<N>, Option<Ciphertext<N>>>;
43    /// The mapping of `serial number` to `tag`.
44    type RecordMap: for<'a> Map<'a, Field<N>, Field<N>>;
45    /// The mapping of `tag` to `serial number`.
46    type RecordTagMap: for<'a> Map<'a, Field<N>, Field<N>>;
47    /// The mapping of `external hash` to `()`. Note: This is **not** the record commitment.
48    type ExternalRecordMap: for<'a> Map<'a, Field<N>, ()>;
49
50    /// Initializes the transition input storage.
51    fn open<S: Into<StorageMode>>(storage: S) -> Result<Self>;
52
53    /// Returns the ID map.
54    fn id_map(&self) -> &Self::IDMap;
55    /// Returns the reverse ID map.
56    fn reverse_id_map(&self) -> &Self::ReverseIDMap;
57    /// Returns the constant map.
58    fn constant_map(&self) -> &Self::ConstantMap;
59    /// Returns the public map.
60    fn public_map(&self) -> &Self::PublicMap;
61    /// Returns the private map.
62    fn private_map(&self) -> &Self::PrivateMap;
63    /// Returns the record map.
64    fn record_map(&self) -> &Self::RecordMap;
65    /// Returns the record tag map.
66    fn record_tag_map(&self) -> &Self::RecordTagMap;
67    /// Returns the external record map.
68    fn external_record_map(&self) -> &Self::ExternalRecordMap;
69
70    /// Returns the storage mode.
71    fn storage_mode(&self) -> &StorageMode;
72
73    /// Starts an atomic batch write operation.
74    fn start_atomic(&self) {
75        self.id_map().start_atomic();
76        self.reverse_id_map().start_atomic();
77        self.constant_map().start_atomic();
78        self.public_map().start_atomic();
79        self.private_map().start_atomic();
80        self.record_map().start_atomic();
81        self.record_tag_map().start_atomic();
82        self.external_record_map().start_atomic();
83    }
84
85    /// Checks if an atomic batch is in progress.
86    fn is_atomic_in_progress(&self) -> bool {
87        self.id_map().is_atomic_in_progress()
88            || self.reverse_id_map().is_atomic_in_progress()
89            || self.constant_map().is_atomic_in_progress()
90            || self.public_map().is_atomic_in_progress()
91            || self.private_map().is_atomic_in_progress()
92            || self.record_map().is_atomic_in_progress()
93            || self.record_tag_map().is_atomic_in_progress()
94            || self.external_record_map().is_atomic_in_progress()
95    }
96
97    /// Checkpoints the atomic batch.
98    fn atomic_checkpoint(&self) {
99        self.id_map().atomic_checkpoint();
100        self.reverse_id_map().atomic_checkpoint();
101        self.constant_map().atomic_checkpoint();
102        self.public_map().atomic_checkpoint();
103        self.private_map().atomic_checkpoint();
104        self.record_map().atomic_checkpoint();
105        self.record_tag_map().atomic_checkpoint();
106        self.external_record_map().atomic_checkpoint();
107    }
108
109    /// Clears the latest atomic batch checkpoint.
110    fn clear_latest_checkpoint(&self) {
111        self.id_map().clear_latest_checkpoint();
112        self.reverse_id_map().clear_latest_checkpoint();
113        self.constant_map().clear_latest_checkpoint();
114        self.public_map().clear_latest_checkpoint();
115        self.private_map().clear_latest_checkpoint();
116        self.record_map().clear_latest_checkpoint();
117        self.record_tag_map().clear_latest_checkpoint();
118        self.external_record_map().clear_latest_checkpoint();
119    }
120
121    /// Rewinds the atomic batch to the previous checkpoint.
122    fn atomic_rewind(&self) {
123        self.id_map().atomic_rewind();
124        self.reverse_id_map().atomic_rewind();
125        self.constant_map().atomic_rewind();
126        self.public_map().atomic_rewind();
127        self.private_map().atomic_rewind();
128        self.record_map().atomic_rewind();
129        self.record_tag_map().atomic_rewind();
130        self.external_record_map().atomic_rewind();
131    }
132
133    /// Aborts an atomic batch write operation.
134    fn abort_atomic(&self) {
135        self.id_map().abort_atomic();
136        self.reverse_id_map().abort_atomic();
137        self.constant_map().abort_atomic();
138        self.public_map().abort_atomic();
139        self.private_map().abort_atomic();
140        self.record_map().abort_atomic();
141        self.record_tag_map().abort_atomic();
142        self.external_record_map().abort_atomic();
143    }
144
145    /// Finishes an atomic batch write operation.
146    fn finish_atomic(&self) -> Result<()> {
147        self.id_map().finish_atomic()?;
148        self.reverse_id_map().finish_atomic()?;
149        self.constant_map().finish_atomic()?;
150        self.public_map().finish_atomic()?;
151        self.private_map().finish_atomic()?;
152        self.record_map().finish_atomic()?;
153        self.record_tag_map().finish_atomic()?;
154        self.external_record_map().finish_atomic()
155    }
156
157    /// Stores the given `(transition ID, input)` pair into storage.
158    fn insert(&self, transition_id: N::TransitionID, inputs: &[Input<N>]) -> Result<()> {
159        atomic_batch_scope!(self, {
160            // Store the input IDs.
161            self.id_map().insert(transition_id, inputs.iter().map(Input::id).copied().collect())?;
162
163            // Store the inputs.
164            for input in inputs {
165                // Store the reverse input ID.
166                self.reverse_id_map().insert(*input.id(), transition_id)?;
167                // Store the input.
168                match input.clone() {
169                    Input::Constant(input_id, constant) => self.constant_map().insert(input_id, constant)?,
170                    Input::Public(input_id, public) => self.public_map().insert(input_id, public)?,
171                    Input::Private(input_id, private) => self.private_map().insert(input_id, private)?,
172                    Input::Record(serial_number, tag) => {
173                        // Store the record tag.
174                        self.record_tag_map().insert(tag, serial_number)?;
175                        // Store the record.
176                        self.record_map().insert(serial_number, tag)?
177                    }
178                    Input::ExternalRecord(input_id) => self.external_record_map().insert(input_id, ())?,
179                }
180            }
181
182            Ok(())
183        })
184    }
185
186    /// Removes the input for the given `transition ID`.
187    fn remove(&self, transition_id: &N::TransitionID) -> Result<()> {
188        // Retrieve the input IDs.
189        let input_ids: Vec<_> = match self.id_map().get_confirmed(transition_id)? {
190            Some(Cow::Borrowed(ids)) => ids.to_vec(),
191            Some(Cow::Owned(ids)) => ids.into_iter().collect(),
192            None => return Ok(()),
193        };
194
195        atomic_batch_scope!(self, {
196            // Remove the input IDs.
197            self.id_map().remove(transition_id)?;
198
199            // Remove the inputs.
200            for input_id in input_ids {
201                // Remove the reverse input ID.
202                self.reverse_id_map().remove(&input_id)?;
203
204                // If the input is a record, remove the record tag.
205                if let Some(tag) = self.record_map().get_confirmed(&input_id)? {
206                    self.record_tag_map().remove(&tag)?;
207                }
208
209                // Remove the input.
210                self.constant_map().remove(&input_id)?;
211                self.public_map().remove(&input_id)?;
212                self.private_map().remove(&input_id)?;
213                self.record_map().remove(&input_id)?;
214                self.external_record_map().remove(&input_id)?;
215            }
216
217            Ok(())
218        })
219    }
220
221    /// Returns the transition ID that contains the given `input ID`.
222    fn find_transition_id(&self, input_id: &Field<N>) -> Result<Option<N::TransitionID>> {
223        match self.reverse_id_map().get_confirmed(input_id)? {
224            Some(Cow::Borrowed(transition_id)) => Ok(Some(*transition_id)),
225            Some(Cow::Owned(transition_id)) => Ok(Some(transition_id)),
226            None => Ok(None),
227        }
228    }
229
230    /// Returns the input IDs for the given `transition ID`.
231    fn get_ids(&self, transition_id: &N::TransitionID) -> Result<Vec<Field<N>>> {
232        // Retrieve the input IDs.
233        match self.id_map().get_confirmed(transition_id)? {
234            Some(Cow::Borrowed(inputs)) => Ok(inputs.to_vec()),
235            Some(Cow::Owned(inputs)) => Ok(inputs),
236            None => Ok(vec![]),
237        }
238    }
239
240    /// Returns the input for the given `transition ID`.
241    fn get(&self, transition_id: &N::TransitionID) -> Result<Vec<Input<N>>> {
242        // Constructs the input given the input ID and input value.
243        macro_rules! into_input {
244            (Input::Record($input_id:ident, $input:expr)) => {
245                match $input {
246                    Cow::Borrowed(tag) => Input::Record($input_id, *tag),
247                    Cow::Owned(tag) => Input::Record($input_id, tag),
248                }
249            };
250            (Input::$Variant:ident($input_id:ident, $input:expr)) => {
251                match $input {
252                    Cow::Borrowed(input) => Input::$Variant($input_id, input.clone()),
253                    Cow::Owned(input) => Input::$Variant($input_id, input),
254                }
255            };
256        }
257
258        // A helper function to construct the input given the input ID.
259        let construct_input = |input_id| {
260            let constant = self.constant_map().get_confirmed(&input_id)?;
261            let public = self.public_map().get_confirmed(&input_id)?;
262            let private = self.private_map().get_confirmed(&input_id)?;
263            let record = self.record_map().get_confirmed(&input_id)?;
264            let external_record = self.external_record_map().get_confirmed(&input_id)?;
265
266            // Retrieve the input.
267            let input = match (constant, public, private, record, external_record) {
268                (Some(constant), None, None, None, None) => into_input!(Input::Constant(input_id, constant)),
269                (None, Some(public), None, None, None) => into_input!(Input::Public(input_id, public)),
270                (None, None, Some(private), None, None) => into_input!(Input::Private(input_id, private)),
271                (None, None, None, Some(record), None) => into_input!(Input::Record(input_id, record)),
272                (None, None, None, None, Some(_)) => Input::ExternalRecord(input_id),
273                (None, None, None, None, None) => bail!("Missing input '{input_id}' in transition '{transition_id}'"),
274                _ => bail!("Found multiple inputs for the input ID '{input_id}' in transition '{transition_id}'"),
275            };
276
277            Ok(input)
278        };
279
280        // Retrieve the input IDs.
281        match self.id_map().get_confirmed(transition_id)? {
282            Some(Cow::Borrowed(ids)) => ids.iter().map(|input_id| construct_input(*input_id)).collect(),
283            Some(Cow::Owned(ids)) => ids.iter().map(|input_id| construct_input(*input_id)).collect(),
284            None => Ok(vec![]),
285        }
286    }
287}
288
289/// The transition input store.
290#[derive(Clone)]
291pub struct InputStore<N: Network, I: InputStorage<N>> {
292    /// The map of constant inputs.
293    constant: I::ConstantMap,
294    /// The map of public inputs.
295    public: I::PublicMap,
296    /// The map of private inputs.
297    private: I::PrivateMap,
298    /// The map of record inputs.
299    record: I::RecordMap,
300    /// The map of record tags.
301    record_tag: I::RecordTagMap,
302    /// The map of external record inputs.
303    external_record: I::ExternalRecordMap,
304    /// The input storage.
305    storage: I,
306}
307
308impl<N: Network, I: InputStorage<N>> InputStore<N, I> {
309    /// Initializes the transition input store.
310    pub fn open<S: Into<StorageMode>>(storage: S) -> Result<Self> {
311        // Initialize a new transition input storage.
312        let storage = I::open(storage)?;
313        // Return the transition input store.
314        Ok(Self {
315            constant: storage.constant_map().clone(),
316            public: storage.public_map().clone(),
317            private: storage.private_map().clone(),
318            record: storage.record_map().clone(),
319            record_tag: storage.record_tag_map().clone(),
320            external_record: storage.external_record_map().clone(),
321            storage,
322        })
323    }
324
325    /// Initializes a transition input store from storage.
326    pub fn from(storage: I) -> Self {
327        Self {
328            constant: storage.constant_map().clone(),
329            public: storage.public_map().clone(),
330            private: storage.private_map().clone(),
331            record: storage.record_map().clone(),
332            record_tag: storage.record_tag_map().clone(),
333            external_record: storage.external_record_map().clone(),
334            storage,
335        }
336    }
337
338    /// Stores the given `(transition ID, input)` pair into storage.
339    pub fn insert(&self, transition_id: N::TransitionID, inputs: &[Input<N>]) -> Result<()> {
340        self.storage.insert(transition_id, inputs)
341    }
342
343    /// Removes the input for the given `transition ID`.
344    pub fn remove(&self, transition_id: &N::TransitionID) -> Result<()> {
345        self.storage.remove(transition_id)
346    }
347
348    /// Starts an atomic batch write operation.
349    pub fn start_atomic(&self) {
350        self.storage.start_atomic();
351    }
352
353    /// Checks if an atomic batch is in progress.
354    pub fn is_atomic_in_progress(&self) -> bool {
355        self.storage.is_atomic_in_progress()
356    }
357
358    /// Checkpoints the atomic batch.
359    pub fn atomic_checkpoint(&self) {
360        self.storage.atomic_checkpoint();
361    }
362
363    /// Clears the latest atomic batch checkpoint.
364    pub fn clear_latest_checkpoint(&self) {
365        self.storage.clear_latest_checkpoint();
366    }
367
368    /// Rewinds the atomic batch to the previous checkpoint.
369    pub fn atomic_rewind(&self) {
370        self.storage.atomic_rewind();
371    }
372
373    /// Aborts an atomic batch write operation.
374    pub fn abort_atomic(&self) {
375        self.storage.abort_atomic();
376    }
377
378    /// Finishes an atomic batch write operation.
379    pub fn finish_atomic(&self) -> Result<()> {
380        self.storage.finish_atomic()
381    }
382
383    /// Returns the storage mode.
384    pub fn storage_mode(&self) -> &StorageMode {
385        self.storage.storage_mode()
386    }
387}
388
389impl<N: Network, I: InputStorage<N>> InputStore<N, I> {
390    /// Returns the input IDs for the given `transition ID`.
391    pub fn get_input_ids(&self, transition_id: &N::TransitionID) -> Result<Vec<Field<N>>> {
392        self.storage.get_ids(transition_id)
393    }
394
395    /// Returns the inputs for the given `transition ID`.
396    pub fn get_inputs(&self, transition_id: &N::TransitionID) -> Result<Vec<Input<N>>> {
397        self.storage.get(transition_id)
398    }
399}
400
401impl<N: Network, I: InputStorage<N>> InputStore<N, I> {
402    /// Returns the transition ID that contains the given `input ID`.
403    pub fn find_transition_id(&self, input_id: &Field<N>) -> Result<Option<N::TransitionID>> {
404        self.storage.find_transition_id(input_id)
405    }
406}
407
408impl<N: Network, I: InputStorage<N>> InputStore<N, I> {
409    /// Returns `true` if the given input ID exists.
410    pub fn contains_input_id(&self, input_id: &Field<N>) -> Result<bool> {
411        self.storage.reverse_id_map().contains_key_confirmed(input_id)
412    }
413
414    /// Returns `true` if the given serial number exists.
415    pub fn contains_serial_number(&self, serial_number: &Field<N>) -> Result<bool> {
416        self.record.contains_key_confirmed(serial_number)
417    }
418
419    /// Returns `true` if the given tag exists.
420    pub fn contains_tag(&self, tag: &Field<N>) -> Result<bool> {
421        self.record_tag.contains_key_confirmed(tag)
422    }
423}
424
425impl<N: Network, I: InputStorage<N>> InputStore<N, I> {
426    /// Returns an iterator over the input IDs, for all transition inputs.
427    pub fn input_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
428        self.storage.reverse_id_map().keys_confirmed()
429    }
430
431    /// Returns an iterator over the constant input IDs, for all transition inputs that are constant.
432    pub fn constant_input_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
433        self.constant.keys_confirmed()
434    }
435
436    /// Returns an iterator over the public input IDs, for all transition inputs that are public.
437    pub fn public_input_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
438        self.public.keys_confirmed()
439    }
440
441    /// Returns an iterator over the private input IDs, for all transition inputs that are private.
442    pub fn private_input_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
443        self.private.keys_confirmed()
444    }
445
446    /// Returns an iterator over the serial numbers, for all transition inputs that are records.
447    pub fn serial_numbers(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
448        self.record.keys_confirmed()
449    }
450
451    /// Returns an iterator over the external record input IDs, for all transition inputs that are external records.
452    pub fn external_input_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
453        self.external_record.keys_confirmed()
454    }
455}
456
457impl<N: Network, I: InputStorage<N>> InputStore<N, I> {
458    /// Returns an iterator over the constant inputs, for all transitions.
459    pub fn constant_inputs(&self) -> impl '_ + Iterator<Item = Cow<'_, Plaintext<N>>> {
460        self.constant.values_confirmed().flat_map(|input| match input {
461            Cow::Borrowed(Some(input)) => Some(Cow::Borrowed(input)),
462            Cow::Owned(Some(input)) => Some(Cow::Owned(input)),
463            _ => None,
464        })
465    }
466
467    /// Returns an iterator over the constant inputs, for all transitions.
468    pub fn public_inputs(&self) -> impl '_ + Iterator<Item = Cow<'_, Plaintext<N>>> {
469        self.public.values_confirmed().flat_map(|input| match input {
470            Cow::Borrowed(Some(input)) => Some(Cow::Borrowed(input)),
471            Cow::Owned(Some(input)) => Some(Cow::Owned(input)),
472            _ => None,
473        })
474    }
475
476    /// Returns an iterator over the private inputs, for all transitions.
477    pub fn private_inputs(&self) -> impl '_ + Iterator<Item = Cow<'_, Ciphertext<N>>> {
478        self.private.values_confirmed().flat_map(|input| match input {
479            Cow::Borrowed(Some(input)) => Some(Cow::Borrowed(input)),
480            Cow::Owned(Some(input)) => Some(Cow::Owned(input)),
481            _ => None,
482        })
483    }
484
485    /// Returns an iterator over the tags, for all transition inputs that are records.
486    pub fn tags(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
487        self.record_tag.keys_confirmed()
488    }
489}
490
491#[cfg(test)]
492mod tests {
493    use super::*;
494    use crate::helpers::memory::InputMemory;
495
496    #[test]
497    fn test_insert_get_remove() {
498        // Sample the transition inputs.
499        for (transition_id, input) in ledger_test_helpers::sample_inputs() {
500            // Initialize a new input store.
501            let input_store = InputMemory::open(StorageMode::Test(None)).unwrap();
502
503            // Ensure the transition input does not exist.
504            let candidate = input_store.get(&transition_id).unwrap();
505            assert!(candidate.is_empty());
506
507            // Insert the transition input.
508            input_store.insert(transition_id, &[input.clone()]).unwrap();
509
510            // Retrieve the transition input.
511            let candidate = input_store.get(&transition_id).unwrap();
512            assert_eq!(vec![input.clone()], candidate);
513
514            // Remove the transition input.
515            input_store.remove(&transition_id).unwrap();
516
517            // Retrieve the transition input.
518            let candidate = input_store.get(&transition_id).unwrap();
519            assert!(candidate.is_empty());
520        }
521    }
522
523    #[test]
524    fn test_find_transition_id() {
525        // Sample the transition inputs.
526        for (transition_id, input) in ledger_test_helpers::sample_inputs() {
527            // Initialize a new input store.
528            let input_store = InputMemory::open(StorageMode::Test(None)).unwrap();
529
530            // Ensure the transition input does not exist.
531            let candidate = input_store.get(&transition_id).unwrap();
532            assert!(candidate.is_empty());
533
534            // Ensure the transition ID is not found.
535            let candidate = input_store.find_transition_id(input.id()).unwrap();
536            assert!(candidate.is_none());
537
538            // Insert the transition input.
539            input_store.insert(transition_id, &[input.clone()]).unwrap();
540
541            // Find the transition ID.
542            let candidate = input_store.find_transition_id(input.id()).unwrap();
543            assert_eq!(Some(transition_id), candidate);
544
545            // Remove the transition input.
546            input_store.remove(&transition_id).unwrap();
547
548            // Ensure the transition ID is not found.
549            let candidate = input_store.find_transition_id(input.id()).unwrap();
550            assert!(candidate.is_none());
551        }
552    }
553}