Skip to main content

nectar_postage/
store.rs

1//! Batch storage traits for persisting batch data.
2
3use crate::{Batch, BatchId, PostageContext};
4
5/// A trait for storing and retrieving batches.
6///
7/// Implementations may persist batches in memory, on disk, or retrieve
8/// them from a remote source such as a blockchain node.
9///
10/// # Async Design
11///
12/// This trait uses async methods to support both local (fast) and
13/// remote (potentially slow) storage backends without blocking.
14pub trait BatchStore {
15    /// The error type returned by store operations.
16    type Error: std::error::Error;
17
18    /// Retrieves a batch by its ID.
19    ///
20    /// Returns `None` if the batch doesn't exist.
21    fn get(
22        &self,
23        id: &BatchId,
24    ) -> impl std::future::Future<Output = Result<Option<Batch>, Self::Error>> + Send;
25
26    /// Stores or updates a batch.
27    fn put(
28        &self,
29        batch: Batch,
30    ) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send;
31
32    /// Removes a batch from the store.
33    ///
34    /// Returns `true` if the batch existed and was removed.
35    fn remove(
36        &self,
37        id: &BatchId,
38    ) -> impl std::future::Future<Output = Result<bool, Self::Error>> + Send;
39
40    /// Checks if a batch exists in the store.
41    fn contains(
42        &self,
43        id: &BatchId,
44    ) -> impl std::future::Future<Output = Result<bool, Self::Error>> + Send;
45
46    /// Returns the current postage context.
47    fn context(
48        &self,
49    ) -> impl std::future::Future<Output = Result<PostageContext, Self::Error>> + Send;
50
51    /// Updates the postage context.
52    fn set_context(
53        &self,
54        state: PostageContext,
55    ) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send;
56
57    /// Returns all batch IDs in the store.
58    fn batch_ids(
59        &self,
60    ) -> impl std::future::Future<Output = Result<Vec<BatchId>, Self::Error>> + Send;
61
62    /// Returns the number of batches in the store.
63    fn count(&self) -> impl std::future::Future<Output = Result<usize, Self::Error>> + Send;
64}
65
66/// Extension methods for [`BatchStore`].
67pub trait BatchStoreExt: BatchStore + Sync {
68    /// Gets a batch and verifies it's usable.
69    ///
70    /// Returns an error if the batch doesn't exist, isn't usable yet,
71    /// or has expired.
72    fn get_usable(
73        &self,
74        id: &BatchId,
75        confirmation_threshold: u64,
76    ) -> impl std::future::Future<Output = Result<Batch, BatchStoreError<Self::Error>>> + Send {
77        async move {
78            let batch = self
79                .get(id)
80                .await
81                .map_err(BatchStoreError::Store)?
82                .ok_or(BatchStoreError::NotFound(*id))?;
83
84            let state = self.context().await.map_err(BatchStoreError::Store)?;
85
86            if !batch.is_usable(state.block(), confirmation_threshold) {
87                return Err(BatchStoreError::NotUsable {
88                    batch_id: *id,
89                    created: batch.start(),
90                    current: state.block(),
91                    threshold: confirmation_threshold,
92                });
93            }
94
95            if batch.is_expired(state.total_amount()) {
96                return Err(BatchStoreError::Expired {
97                    batch_id: *id,
98                    value: batch.value(),
99                    total_amount: state.total_amount(),
100                });
101            }
102
103            Ok(batch)
104        }
105    }
106}
107
108// Blanket implementation
109impl<T: BatchStore + Sync> BatchStoreExt for T {}
110
111/// Errors that can occur when working with a batch store.
112#[derive(Debug)]
113pub enum BatchStoreError<E> {
114    /// The batch was not found in the store.
115    NotFound(BatchId),
116    /// The batch is not yet usable (needs more confirmations).
117    NotUsable {
118        /// The batch ID.
119        batch_id: BatchId,
120        /// Block when batch was created.
121        created: u64,
122        /// Current block number.
123        current: u64,
124        /// Required confirmations.
125        threshold: u64,
126    },
127    /// The batch has expired.
128    Expired {
129        /// The batch ID.
130        batch_id: BatchId,
131        /// Current batch value.
132        value: u128,
133        /// Total amount consumed.
134        total_amount: u128,
135    },
136    /// An error from the underlying store.
137    Store(E),
138}
139
140impl<E: std::fmt::Display> std::fmt::Display for BatchStoreError<E> {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        match self {
143            Self::NotFound(id) => write!(f, "batch not found: {}", id),
144            Self::NotUsable {
145                batch_id,
146                created,
147                current,
148                threshold,
149            } => write!(
150                f,
151                "batch {} not usable: created at block {}, current block {}, need {} confirmations",
152                batch_id, created, current, threshold
153            ),
154            Self::Expired {
155                batch_id,
156                value,
157                total_amount,
158            } => write!(
159                f,
160                "batch {} expired: value {} <= total_amount {}",
161                batch_id, value, total_amount
162            ),
163            Self::Store(e) => write!(f, "store error: {}", e),
164        }
165    }
166}
167
168impl<E: std::error::Error + 'static> std::error::Error for BatchStoreError<E> {
169    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
170        match self {
171            Self::Store(e) => Some(e),
172            _ => None,
173        }
174    }
175}