snarkvm_ledger_store/transaction/
fee.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    TransitionStorage,
18    TransitionStore,
19    atomic_batch_scope,
20    cow_to_cloned,
21    cow_to_copied,
22    helpers::{Map, MapRead},
23};
24use console::network::prelude::*;
25use ledger_block::Fee;
26use synthesizer_snark::Proof;
27
28use aleo_std_storage::StorageMode;
29use anyhow::Result;
30use core::marker::PhantomData;
31
32/// A trait for fee storage.
33pub trait FeeStorage<N: Network>: Clone + Send + Sync {
34    /// The mapping of `transaction ID` to `(fee transition ID, global state root, proof)`.
35    type FeeMap: for<'a> Map<'a, N::TransactionID, (N::TransitionID, N::StateRoot, Option<Proof<N>>)>;
36    /// The mapping of `fee transition ID` to `transaction ID`.
37    type ReverseFeeMap: for<'a> Map<'a, N::TransitionID, N::TransactionID>;
38
39    /// The transition storage.
40    type TransitionStorage: TransitionStorage<N>;
41
42    /// Initializes the fee storage.
43    fn open(transition_store: TransitionStore<N, Self::TransitionStorage>) -> Result<Self>;
44
45    /// Returns the fee map.
46    fn fee_map(&self) -> &Self::FeeMap;
47    /// Returns the reverse fee map.
48    fn reverse_fee_map(&self) -> &Self::ReverseFeeMap;
49    /// Returns the transition storage.
50    fn transition_store(&self) -> &TransitionStore<N, Self::TransitionStorage>;
51
52    /// Returns the storage mode.
53    fn storage_mode(&self) -> &StorageMode {
54        self.transition_store().storage_mode()
55    }
56
57    /// Starts an atomic batch write operation.
58    fn start_atomic(&self) {
59        self.fee_map().start_atomic();
60        self.reverse_fee_map().start_atomic();
61        self.transition_store().start_atomic();
62    }
63
64    /// Checks if an atomic batch is in progress.
65    fn is_atomic_in_progress(&self) -> bool {
66        self.fee_map().is_atomic_in_progress()
67            || self.reverse_fee_map().is_atomic_in_progress()
68            || self.transition_store().is_atomic_in_progress()
69    }
70
71    /// Checkpoints the atomic batch.
72    fn atomic_checkpoint(&self) {
73        self.fee_map().atomic_checkpoint();
74        self.reverse_fee_map().atomic_checkpoint();
75        self.transition_store().atomic_checkpoint();
76    }
77
78    /// Clears the latest atomic batch checkpoint.
79    fn clear_latest_checkpoint(&self) {
80        self.fee_map().clear_latest_checkpoint();
81        self.reverse_fee_map().clear_latest_checkpoint();
82        self.transition_store().clear_latest_checkpoint();
83    }
84
85    /// Rewinds the atomic batch to the previous checkpoint.
86    fn atomic_rewind(&self) {
87        self.fee_map().atomic_rewind();
88        self.reverse_fee_map().atomic_rewind();
89        self.transition_store().atomic_rewind();
90    }
91
92    /// Aborts an atomic batch write operation.
93    fn abort_atomic(&self) {
94        self.fee_map().abort_atomic();
95        self.reverse_fee_map().abort_atomic();
96        self.transition_store().abort_atomic();
97    }
98
99    /// Finishes an atomic batch write operation.
100    fn finish_atomic(&self) -> Result<()> {
101        self.fee_map().finish_atomic()?;
102        self.reverse_fee_map().finish_atomic()?;
103        self.transition_store().finish_atomic()
104    }
105
106    /// Stores the given `(transaction ID, fee)` pair into storage.
107    fn insert(&self, transaction_id: N::TransactionID, fee: &Fee<N>) -> Result<()> {
108        atomic_batch_scope!(self, {
109            // Store the fee.
110            self.fee_map()
111                .insert(transaction_id, (*fee.transition_id(), fee.global_state_root(), fee.proof().cloned()))?;
112            self.reverse_fee_map().insert(*fee.transition_id(), transaction_id)?;
113
114            // Store the fee transition.
115            self.transition_store().insert(fee)?;
116
117            Ok(())
118        })
119    }
120
121    /// Removes the fee for the given `transaction ID`.
122    fn remove(&self, transaction_id: &N::TransactionID) -> Result<()> {
123        // Retrieve the fee transition ID.
124        let (transition_id, _, _) = match self.fee_map().get_confirmed(transaction_id)? {
125            Some(fee_id) => cow_to_cloned!(fee_id),
126            None => bail!("Failed to locate the fee transition ID for transaction '{transaction_id}'"),
127        };
128
129        atomic_batch_scope!(self, {
130            // Remove the fee.
131            self.fee_map().remove(transaction_id)?;
132            self.reverse_fee_map().remove(&transition_id)?;
133
134            // Remove the fee transition.
135            self.transition_store().remove(&transition_id)?;
136
137            Ok(())
138        })
139    }
140
141    /// Returns the transaction ID that contains the given `transition ID`.
142    fn find_transaction_id_from_transition_id(
143        &self,
144        transition_id: &N::TransitionID,
145    ) -> Result<Option<N::TransactionID>> {
146        match self.reverse_fee_map().get_confirmed(transition_id)? {
147            Some(transaction_id) => Ok(Some(cow_to_copied!(transaction_id))),
148            None => Ok(None),
149        }
150    }
151
152    /// Returns the fee for the given `transaction ID`.
153    fn get_fee(&self, transaction_id: &N::TransactionID) -> Result<Option<Fee<N>>> {
154        // Retrieve the fee transition ID.
155        let (fee_transition_id, global_state_root, proof) = match self.fee_map().get_confirmed(transaction_id)? {
156            Some(fee) => cow_to_cloned!(fee),
157            None => return Ok(None),
158        };
159        // Retrieve the fee transition.
160        match self.transition_store().get_transition(&fee_transition_id)? {
161            Some(transition) => Ok(Some(Fee::from_unchecked(transition, global_state_root, proof))),
162            None => bail!("Failed to locate the fee transition for transaction '{transaction_id}'"),
163        }
164    }
165}
166
167/// The fee store.
168#[derive(Clone)]
169pub struct FeeStore<N: Network, F: FeeStorage<N>> {
170    /// The fee storage.
171    storage: F,
172    /// PhantomData.
173    _phantom: PhantomData<N>,
174}
175
176impl<N: Network, F: FeeStorage<N>> FeeStore<N, F> {
177    /// Initializes the fee store.
178    pub fn open(transition_store: TransitionStore<N, F::TransitionStorage>) -> Result<Self> {
179        // Initialize the fee storage.
180        let storage = F::open(transition_store)?;
181        // Return the fee store.
182        Ok(Self { storage, _phantom: PhantomData })
183    }
184
185    /// Initializes a fee store from storage.
186    pub fn from(storage: F) -> Self {
187        Self { storage, _phantom: PhantomData }
188    }
189
190    /// Stores the given `(transaction_id, fee)` into storage.
191    pub fn insert(&self, transaction_id: N::TransactionID, fee: &Fee<N>) -> Result<()> {
192        self.storage.insert(transaction_id, fee)
193    }
194
195    /// Removes the fee for the given `transaction ID`.
196    pub fn remove(&self, transaction_id: &N::TransactionID) -> Result<()> {
197        self.storage.remove(transaction_id)
198    }
199
200    /// Returns the transition store.
201    pub fn transition_store(&self) -> &TransitionStore<N, F::TransitionStorage> {
202        self.storage.transition_store()
203    }
204
205    /// Starts an atomic batch write operation.
206    pub fn start_atomic(&self) {
207        self.storage.start_atomic();
208    }
209
210    /// Checks if an atomic batch is in progress.
211    pub fn is_atomic_in_progress(&self) -> bool {
212        self.storage.is_atomic_in_progress()
213    }
214
215    /// Checkpoints the atomic batch.
216    pub fn atomic_checkpoint(&self) {
217        self.storage.atomic_checkpoint();
218    }
219
220    /// Clears the latest atomic batch checkpoint.
221    pub fn clear_latest_checkpoint(&self) {
222        self.storage.clear_latest_checkpoint();
223    }
224
225    /// Rewinds the atomic batch to the previous checkpoint.
226    pub fn atomic_rewind(&self) {
227        self.storage.atomic_rewind();
228    }
229
230    /// Aborts an atomic batch write operation.
231    pub fn abort_atomic(&self) {
232        self.storage.abort_atomic();
233    }
234
235    /// Finishes an atomic batch write operation.
236    pub fn finish_atomic(&self) -> Result<()> {
237        self.storage.finish_atomic()
238    }
239
240    /// Returns the storage mode.
241    pub fn storage_mode(&self) -> &StorageMode {
242        self.storage.storage_mode()
243    }
244}
245
246impl<N: Network, F: FeeStorage<N>> FeeStore<N, F> {
247    /// Returns the fee for the given `transaction ID`.
248    pub fn get_fee(&self, transaction_id: &N::TransactionID) -> Result<Option<Fee<N>>> {
249        self.storage.get_fee(transaction_id)
250    }
251}
252
253impl<N: Network, F: FeeStorage<N>> FeeStore<N, F> {
254    /// Returns the transaction ID that deployed the given `transition ID`.
255    pub fn find_transaction_id_from_transition_id(
256        &self,
257        transition_id: &N::TransitionID,
258    ) -> Result<Option<N::TransactionID>> {
259        self.storage.find_transaction_id_from_transition_id(transition_id)
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266    use crate::helpers::memory::FeeMemory;
267    use ledger_block::Transaction;
268
269    #[test]
270    fn test_insert_get_remove() {
271        let rng = &mut TestRng::default();
272
273        // Sample the fee transactions.
274        let transaction_0 = ledger_test_helpers::sample_fee_private_transaction(rng);
275        let transaction_1 = ledger_test_helpers::sample_fee_public_transaction(rng);
276        let transactions = vec![transaction_0, transaction_1];
277
278        for transaction in transactions {
279            let (transaction_id, fee) = match transaction {
280                Transaction::Fee(id, fee) => (id, fee),
281                _ => unreachable!("Invalid transaction type - expected a fee transaction"),
282            };
283
284            // Initialize a new transition store.
285            let transition_store = TransitionStore::open(StorageMode::Test(None)).unwrap();
286            // Initialize a new fee store.
287            let fee_store = FeeMemory::open(transition_store).unwrap();
288
289            // Ensure the fee transaction does not exist.
290            let candidate = fee_store.get_fee(&transaction_id).unwrap();
291            assert_eq!(None, candidate);
292
293            // Insert the fee transaction.
294            fee_store.insert(transaction_id, &fee).unwrap();
295
296            // Retrieve the fee.
297            let candidate = fee_store.get_fee(&transaction_id).unwrap();
298            assert_eq!(Some(fee), candidate);
299
300            // Remove the fee transaction.
301            fee_store.remove(&transaction_id).unwrap();
302
303            // Ensure the fee does not exist.
304            let candidate = fee_store.get_fee(&transaction_id).unwrap();
305            assert_eq!(None, candidate);
306        }
307    }
308
309    #[test]
310    fn test_find_transaction_id() {
311        let rng = &mut TestRng::default();
312
313        // Sample the fee transactions.
314        let transaction_0 = ledger_test_helpers::sample_fee_private_transaction(rng);
315        let transaction_1 = ledger_test_helpers::sample_fee_public_transaction(rng);
316        let transactions = vec![transaction_0, transaction_1];
317
318        for transaction in transactions {
319            let (transaction_id, fee) = match transaction {
320                Transaction::Fee(id, fee) => (id, fee),
321                _ => unreachable!("Invalid transaction type - expected a fee transaction"),
322            };
323            let fee_transition_id = fee.id();
324
325            // Initialize a new transition store.
326            let transition_store = TransitionStore::open(StorageMode::Test(None)).unwrap();
327            // Initialize a new fee store.
328            let fee_store = FeeMemory::open(transition_store).unwrap();
329
330            // Ensure the fee does not exist.
331            let candidate = fee_store.get_fee(&transaction_id).unwrap();
332            assert_eq!(None, candidate);
333
334            // Ensure the transaction ID is not found.
335            let candidate = fee_store.find_transaction_id_from_transition_id(fee_transition_id).unwrap();
336            assert_eq!(None, candidate);
337
338            // Insert the fee transaction.
339            fee_store.insert(transaction_id, &fee).unwrap();
340
341            // Find the transaction ID.
342            let candidate = fee_store.find_transaction_id_from_transition_id(fee_transition_id).unwrap();
343            assert_eq!(Some(transaction_id), candidate);
344
345            // Remove the fee transaction.
346            fee_store.remove(&transaction_id).unwrap();
347
348            // Ensure the transaction ID is not found.
349            let candidate = fee_store.find_transaction_id_from_transition_id(fee_transition_id).unwrap();
350            assert_eq!(None, candidate);
351        }
352    }
353}