snarkvm_ledger_store/transaction/
execution.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    FeeStorage,
18    FeeStore,
19    TransitionStore,
20    atomic_batch_scope,
21    cow_to_cloned,
22    cow_to_copied,
23    helpers::{Map, MapRead},
24};
25use console::network::prelude::*;
26use ledger_block::{Execution, Transaction, Transition};
27use synthesizer_snark::Proof;
28
29use aleo_std_storage::StorageMode;
30use anyhow::Result;
31use core::marker::PhantomData;
32use std::borrow::Cow;
33
34/// A trait for execution storage.
35pub trait ExecutionStorage<N: Network>: Clone + Send + Sync {
36    /// The mapping of `transaction ID` to `([transition ID], has_fee)`.
37    type IDMap: for<'a> Map<'a, N::TransactionID, (Vec<N::TransitionID>, bool)>;
38    /// The mapping of `transition ID` to `transaction ID`.
39    type ReverseIDMap: for<'a> Map<'a, N::TransitionID, N::TransactionID>;
40    /// The mapping of `transaction ID` to `(global state root, (optional) proof)`.
41    type InclusionMap: for<'a> Map<'a, N::TransactionID, (N::StateRoot, Option<Proof<N>>)>;
42    /// The fee storage.
43    type FeeStorage: FeeStorage<N>;
44
45    /// Initializes the execution storage.
46    fn open(fee_store: FeeStore<N, Self::FeeStorage>) -> Result<Self>;
47
48    /// Returns the ID map.
49    fn id_map(&self) -> &Self::IDMap;
50    /// Returns the reverse ID map.
51    fn reverse_id_map(&self) -> &Self::ReverseIDMap;
52    /// Returns the inclusion map.
53    fn inclusion_map(&self) -> &Self::InclusionMap;
54    /// Returns the fee store.
55    fn fee_store(&self) -> &FeeStore<N, Self::FeeStorage>;
56    /// Returns the transition store.
57    fn transition_store(&self) -> &TransitionStore<N, <Self::FeeStorage as FeeStorage<N>>::TransitionStorage> {
58        self.fee_store().transition_store()
59    }
60
61    /// Returns the storage mode.
62    fn storage_mode(&self) -> &StorageMode {
63        self.transition_store().storage_mode()
64    }
65
66    /// Starts an atomic batch write operation.
67    fn start_atomic(&self) {
68        self.id_map().start_atomic();
69        self.reverse_id_map().start_atomic();
70        self.inclusion_map().start_atomic();
71        self.fee_store().start_atomic();
72    }
73
74    /// Checks if an atomic batch is in progress.
75    fn is_atomic_in_progress(&self) -> bool {
76        self.id_map().is_atomic_in_progress()
77            || self.reverse_id_map().is_atomic_in_progress()
78            || self.inclusion_map().is_atomic_in_progress()
79            || self.fee_store().is_atomic_in_progress()
80    }
81
82    /// Checkpoints the atomic batch.
83    fn atomic_checkpoint(&self) {
84        self.id_map().atomic_checkpoint();
85        self.reverse_id_map().atomic_checkpoint();
86        self.inclusion_map().atomic_checkpoint();
87        self.fee_store().atomic_checkpoint();
88    }
89
90    /// Clears the latest atomic batch checkpoint.
91    fn clear_latest_checkpoint(&self) {
92        self.id_map().clear_latest_checkpoint();
93        self.reverse_id_map().clear_latest_checkpoint();
94        self.inclusion_map().clear_latest_checkpoint();
95        self.fee_store().clear_latest_checkpoint();
96    }
97
98    /// Rewinds the atomic batch to the previous checkpoint.
99    fn atomic_rewind(&self) {
100        self.id_map().atomic_rewind();
101        self.reverse_id_map().atomic_rewind();
102        self.inclusion_map().atomic_rewind();
103        self.fee_store().atomic_rewind();
104    }
105
106    /// Aborts an atomic batch write operation.
107    fn abort_atomic(&self) {
108        self.id_map().abort_atomic();
109        self.reverse_id_map().abort_atomic();
110        self.inclusion_map().abort_atomic();
111        self.fee_store().abort_atomic();
112    }
113
114    /// Finishes an atomic batch write operation.
115    fn finish_atomic(&self) -> Result<()> {
116        self.id_map().finish_atomic()?;
117        self.reverse_id_map().finish_atomic()?;
118        self.inclusion_map().finish_atomic()?;
119        self.fee_store().finish_atomic()
120    }
121
122    /// Stores the given `execution transaction` pair into storage.
123    fn insert(&self, transaction: &Transaction<N>) -> Result<()> {
124        // Ensure the transaction is a execution.
125        let (transaction_id, execution, fee) = match transaction {
126            Transaction::Deploy(..) => bail!("Attempted to insert a deploy transaction into execution storage."),
127            Transaction::Execute(transaction_id, _, execution, fee) => (transaction_id, execution, fee),
128            Transaction::Fee(..) => bail!("Attempted to insert a fee transaction into execution storage."),
129        };
130
131        // Retrieve the transitions.
132        let transitions = execution.transitions();
133        // Retrieve the transition IDs.
134        let transition_ids = execution.transitions().map(Transition::id).copied().collect();
135        // Retrieve the global state root.
136        let global_state_root = execution.global_state_root();
137        // Retrieve the proof.
138        let proof = execution.proof().cloned();
139
140        atomic_batch_scope!(self, {
141            // Store the transition IDs.
142            self.id_map().insert(*transaction_id, (transition_ids, fee.is_some()))?;
143
144            // Store the execution.
145            for transition in transitions {
146                // Store the transition ID.
147                self.reverse_id_map().insert(*transition.id(), *transaction_id)?;
148                // Store the transition.
149                self.transition_store().insert(transition)?;
150            }
151
152            // Store the global state root and proof.
153            self.inclusion_map().insert(*transaction_id, (global_state_root, proof))?;
154
155            // Store the fee.
156            if let Some(fee) = fee {
157                // Store the fee.
158                self.fee_store().insert(*transaction_id, fee)?;
159            }
160
161            Ok(())
162        })
163    }
164
165    /// Removes the execution transaction for the given `transaction ID`.
166    fn remove(&self, transaction_id: &N::TransactionID) -> Result<()> {
167        // Retrieve the transition IDs and fee boolean.
168        let (transition_ids, has_fee) = match self.id_map().get_confirmed(transaction_id)? {
169            Some(ids) => cow_to_cloned!(ids),
170            None => bail!("Failed to get the transition IDs for the transaction '{transaction_id}'"),
171        };
172
173        atomic_batch_scope!(self, {
174            // Remove the transition IDs.
175            self.id_map().remove(transaction_id)?;
176
177            // Remove the execution.
178            for transition_id in transition_ids {
179                // Remove the transition ID.
180                self.reverse_id_map().remove(&transition_id)?;
181                // Remove the transition.
182                self.transition_store().remove(&transition_id)?;
183            }
184
185            // Remove the global state root and proof.
186            self.inclusion_map().remove(transaction_id)?;
187
188            // Remove the fee.
189            if has_fee {
190                // Remove the fee.
191                self.fee_store().remove(transaction_id)?;
192            }
193
194            Ok(())
195        })
196    }
197
198    /// Returns the transaction ID that contains the given `transition ID`.
199    fn find_transaction_id_from_transition_id(
200        &self,
201        transition_id: &N::TransitionID,
202    ) -> Result<Option<N::TransactionID>> {
203        // First, check if the transition ID is in the fee store.
204        if let Some(transaction_id) = self.fee_store().find_transaction_id_from_transition_id(transition_id)? {
205            return Ok(Some(transaction_id));
206        }
207        // Otherwise, check if the transition ID is in the reverse ID map.
208        match self.reverse_id_map().get_confirmed(transition_id)? {
209            Some(transaction_id) => Ok(Some(cow_to_copied!(transaction_id))),
210            None => Ok(None),
211        }
212    }
213
214    /// Returns the execution for the given `transaction ID`.
215    fn get_execution(&self, transaction_id: &N::TransactionID) -> Result<Option<Execution<N>>> {
216        // Retrieve the transition IDs.
217        let (transition_ids, _) = match self.id_map().get_confirmed(transaction_id)? {
218            Some(ids) => cow_to_cloned!(ids),
219            None => return Ok(None),
220        };
221
222        // Retrieve the global state root and proof.
223        let (global_state_root, proof) = match self.inclusion_map().get_confirmed(transaction_id)? {
224            Some(inclusion) => cow_to_cloned!(inclusion),
225            None => bail!("Failed to get the proof for the transaction '{transaction_id}'"),
226        };
227
228        // Initialize a vector for the transitions.
229        let mut transitions = Vec::new();
230
231        // Retrieve the transitions.
232        for transition_id in &transition_ids {
233            match self.transition_store().get_transition(transition_id)? {
234                Some(transition) => transitions.push(transition),
235                None => bail!("Failed to get transition '{transition_id}' for transaction '{transaction_id}'"),
236            };
237        }
238
239        // Return the execution.
240        Ok(Some(Execution::from(transitions.into_iter(), global_state_root, proof)?))
241    }
242
243    /// Returns the transaction for the given `transaction ID`.
244    fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
245        // Retrieve the transition IDs and fee boolean.
246        let (transition_ids, has_fee) = match self.id_map().get_confirmed(transaction_id)? {
247            Some(ids) => cow_to_cloned!(ids),
248            None => return Ok(None),
249        };
250
251        // Retrieve the global state root and proof.
252        let (global_state_root, proof) = match self.inclusion_map().get_confirmed(transaction_id)? {
253            Some(inclusion) => cow_to_cloned!(inclusion),
254            None => bail!("Failed to get the proof for the transaction '{transaction_id}'"),
255        };
256
257        // Initialize a vector for the transitions.
258        let mut transitions = Vec::new();
259
260        // Retrieve the transitions.
261        for transition_id in &transition_ids {
262            match self.transition_store().get_transition(transition_id)? {
263                Some(transition) => transitions.push(transition),
264                None => bail!("Failed to get transition '{transition_id}' for transaction '{transaction_id}'"),
265            };
266        }
267
268        // Construct the execution.
269        let execution = Execution::from(transitions.into_iter(), global_state_root, proof)?;
270
271        // Construct the transaction.
272        let transaction = match has_fee {
273            // Retrieve the fee.
274            true => match self.fee_store().get_fee(transaction_id)? {
275                // Construct the transaction.
276                Some(fee) => Transaction::from_execution(execution, Some(fee))?,
277                None => bail!("Failed to get the fee for transaction '{transaction_id}'"),
278            },
279            false => Transaction::from_execution(execution, None)?,
280        };
281
282        // Ensure the transaction ID matches.
283        match *transaction_id == transaction.id() {
284            true => Ok(Some(transaction)),
285            false => bail!("Mismatching transaction ID for transaction '{transaction_id}'"),
286        }
287    }
288}
289
290/// The execution store.
291#[derive(Clone)]
292pub struct ExecutionStore<N: Network, E: ExecutionStorage<N>> {
293    /// The execution storage.
294    storage: E,
295    /// PhantomData.
296    _phantom: PhantomData<N>,
297}
298
299impl<N: Network, E: ExecutionStorage<N>> ExecutionStore<N, E> {
300    /// Initializes the execution store.
301    pub fn open(fee_store: FeeStore<N, E::FeeStorage>) -> Result<Self> {
302        // Initialize the execution storage.
303        let storage = E::open(fee_store)?;
304        // Return the execution store.
305        Ok(Self { storage, _phantom: PhantomData })
306    }
307
308    /// Initializes an execution store from storage.
309    pub fn from(storage: E) -> Self {
310        Self { storage, _phantom: PhantomData }
311    }
312
313    /// Stores the given `execution transaction` into storage.
314    pub fn insert(&self, transaction: &Transaction<N>) -> Result<()> {
315        self.storage.insert(transaction)
316    }
317
318    /// Removes the transaction for the given `transaction ID`.
319    pub fn remove(&self, transaction_id: &N::TransactionID) -> Result<()> {
320        self.storage.remove(transaction_id)
321    }
322
323    /// Starts an atomic batch write operation.
324    pub fn start_atomic(&self) {
325        self.storage.start_atomic();
326    }
327
328    /// Checks if an atomic batch is in progress.
329    pub fn is_atomic_in_progress(&self) -> bool {
330        self.storage.is_atomic_in_progress()
331    }
332
333    /// Checkpoints the atomic batch.
334    pub fn atomic_checkpoint(&self) {
335        self.storage.atomic_checkpoint();
336    }
337
338    /// Clears the latest atomic batch checkpoint.
339    pub fn clear_latest_checkpoint(&self) {
340        self.storage.clear_latest_checkpoint();
341    }
342
343    /// Rewinds the atomic batch to the previous checkpoint.
344    pub fn atomic_rewind(&self) {
345        self.storage.atomic_rewind();
346    }
347
348    /// Aborts an atomic batch write operation.
349    pub fn abort_atomic(&self) {
350        self.storage.abort_atomic();
351    }
352
353    /// Finishes an atomic batch write operation.
354    pub fn finish_atomic(&self) -> Result<()> {
355        self.storage.finish_atomic()
356    }
357
358    /// Returns the storage mode.
359    pub fn storage_mode(&self) -> &StorageMode {
360        self.storage.storage_mode()
361    }
362}
363
364impl<N: Network, E: ExecutionStorage<N>> ExecutionStore<N, E> {
365    /// Returns the transaction for the given `transaction ID`.
366    pub fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
367        self.storage.get_transaction(transaction_id)
368    }
369
370    /// Returns the execution for the given `transaction ID`.
371    pub fn get_execution(&self, transaction_id: &N::TransactionID) -> Result<Option<Execution<N>>> {
372        self.storage.get_execution(transaction_id)
373    }
374}
375
376impl<N: Network, E: ExecutionStorage<N>> ExecutionStore<N, E> {
377    /// Returns the transaction ID that executed the given `transition ID`.
378    pub fn find_transaction_id_from_transition_id(
379        &self,
380        transition_id: &N::TransitionID,
381    ) -> Result<Option<N::TransactionID>> {
382        self.storage.find_transaction_id_from_transition_id(transition_id)
383    }
384}
385
386impl<N: Network, E: ExecutionStorage<N>> ExecutionStore<N, E> {
387    /// Returns an iterator over the execution transaction IDs, for all executions.
388    pub fn execution_transaction_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, N::TransactionID>> {
389        self.storage.id_map().keys_confirmed()
390    }
391}
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396    use crate::{TransitionStore, helpers::memory::ExecutionMemory};
397
398    type CurrentNetwork = console::network::MainnetV0;
399
400    fn insert_get_remove(transaction: Transaction<CurrentNetwork>) -> Result<()> {
401        let transaction_id = transaction.id();
402
403        // Initialize a new transition store.
404        let transition_store = TransitionStore::open(StorageMode::Test(None))?;
405        // Initialize a new fee store.
406        let fee_store = FeeStore::open(transition_store).unwrap();
407        // Initialize a new execution store.
408        let execution_store = ExecutionMemory::open(fee_store)?;
409
410        // Ensure the execution transaction does not exist.
411        let candidate = execution_store.get_transaction(&transaction_id)?;
412        assert_eq!(None, candidate);
413
414        // Insert the execution transaction.
415        execution_store.insert(&transaction)?;
416
417        // Retrieve the execution transaction.
418        let candidate = execution_store.get_transaction(&transaction_id)?;
419        assert_eq!(Some(transaction), candidate);
420
421        // Remove the execution.
422        execution_store.remove(&transaction_id)?;
423
424        // Ensure the execution transaction does not exist.
425        let candidate = execution_store.get_transaction(&transaction_id)?;
426        assert_eq!(None, candidate);
427
428        Ok(())
429    }
430
431    fn find_transaction_id(transaction: Transaction<CurrentNetwork>) -> Result<()> {
432        let transaction_id = transaction.id();
433
434        // Ensure the transaction is an Execution.
435        if matches!(transaction, Transaction::Deploy(..)) {
436            bail!("Invalid transaction type");
437        }
438
439        // Initialize a new transition store.
440        let transition_store = TransitionStore::open(StorageMode::Test(None))?;
441        // Initialize a new fee store.
442        let fee_store = FeeStore::open(transition_store).unwrap();
443        // Initialize a new execution store.
444        let execution_store = ExecutionMemory::open(fee_store)?;
445
446        // Ensure the execution transaction does not exist.
447        let candidate = execution_store.get_transaction(&transaction_id)?;
448        assert_eq!(None, candidate);
449
450        for transition_id in transaction.transition_ids() {
451            // Ensure the transaction ID is not found.
452            let candidate = execution_store.find_transaction_id_from_transition_id(transition_id).unwrap();
453            assert_eq!(None, candidate);
454
455            // Insert the execution.
456            execution_store.insert(&transaction)?;
457
458            // Find the transaction ID.
459            let candidate = execution_store.find_transaction_id_from_transition_id(transition_id).unwrap();
460            assert_eq!(Some(transaction_id), candidate);
461
462            // Remove the execution.
463            execution_store.remove(&transaction_id)?;
464
465            // Ensure the transaction ID is not found.
466            let candidate = execution_store.find_transaction_id_from_transition_id(transition_id).unwrap();
467            assert_eq!(None, candidate);
468        }
469
470        Ok(())
471    }
472
473    #[test]
474    fn test_insert_get_remove() {
475        let rng = &mut TestRng::default();
476
477        // Sample the execution transaction.
478        let transaction = ledger_test_helpers::sample_execution_transaction_with_fee(true, rng);
479        insert_get_remove(transaction).unwrap();
480
481        // Sample the execution transaction.
482        let transaction = ledger_test_helpers::sample_execution_transaction_with_fee(false, rng);
483        insert_get_remove(transaction).unwrap();
484    }
485
486    #[test]
487    fn test_find_transaction_id() {
488        let rng = &mut TestRng::default();
489
490        // Sample the execution transaction.
491        let transaction = ledger_test_helpers::sample_execution_transaction_with_fee(true, rng);
492        find_transaction_id(transaction).unwrap();
493
494        // Sample the execution transaction.
495        let transaction = ledger_test_helpers::sample_execution_transaction_with_fee(false, rng);
496        find_transaction_id(transaction).unwrap();
497    }
498}