penumbra_tct/
storage.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
//! Incremental serialization and non-incremental deserialization for the [`Tree`](crate::Tree).

use std::{
    collections::{btree_map::Entry, BTreeMap},
    fmt::Debug,
    ops::Range,
};

use futures::Stream;

use crate::prelude::*;

pub(crate) mod deserialize;
pub(crate) mod serialize;

pub mod in_memory;
pub use deserialize::{LoadCommitments, LoadHashes};
pub use in_memory::InMemory;

/// A stored position for the tree: either the position of the tree, or a marker indicating that it
/// is full, and therefore does not have a position.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum StoredPosition {
    /// The tree has the given position.
    Position(Position),
    /// The tree is full.
    Full,
}

impl Default for StoredPosition {
    fn default() -> Self {
        StoredPosition::Position(Position::default())
    }
}

impl From<StoredPosition> for Option<Position> {
    fn from(stored: StoredPosition) -> Self {
        match stored {
            StoredPosition::Position(position) => Some(position),
            StoredPosition::Full => None,
        }
    }
}

impl From<Option<Position>> for StoredPosition {
    fn from(position: Option<Position>) -> Self {
        match position {
            Some(position) => StoredPosition::Position(position),
            None => StoredPosition::Full,
        }
    }
}

/// An `async` storage backend capable of reading stored [`struct@Hash`]es and [`Commitment`]s as
/// well as storing the current [`Position`].
#[async_trait]
pub trait AsyncRead {
    /// The error returned when something goes wrong in a request.
    type Error;

    /// The type of stream returned by [`AsyncRead::hashes`].
    type HashesStream<'a>: Stream<Item = Result<(Position, u8, Hash), Self::Error>> + Unpin + 'a
    where
        Self: 'a;

    /// The type of stream returned by [`AsyncRead::commitments`].
    type CommitmentsStream<'a>: Stream<Item = Result<(Position, StateCommitment), Self::Error>>
        + Unpin
        + 'a
    where
        Self: 'a;

    /// Fetch the current position stored.
    async fn position(&mut self) -> Result<StoredPosition, Self::Error>;

    /// Fetch the current forgotten version.
    async fn forgotten(&mut self) -> Result<Forgotten, Self::Error>;

    /// Fetch the hash at the given position and height, if it exists.
    async fn hash(&mut self, position: Position, height: u8) -> Result<Option<Hash>, Self::Error>;

    /// Get the full list of all internal hashes stored, indexed by position and height.
    fn hashes(&mut self) -> Self::HashesStream<'_>;

    /// Fetch the commitment at the given position, if it exists.
    async fn commitment(
        &mut self,
        position: Position,
    ) -> Result<Option<StateCommitment>, Self::Error>;

    /// Get the full list of all commitments stored, indexed by position.
    fn commitments(&mut self) -> Self::CommitmentsStream<'_>;
}

/// An `async` storage backend capable of writing [`struct@Hash`]es and [`Commitment`]s, and
/// garbage-collecting those which have been forgotten.
#[async_trait]
pub trait AsyncWrite: AsyncRead {
    /// Write a single hash into storage.
    ///
    /// Backends are only *required* to persist hashes marked as `essential`. They may choose to
    /// persist other hashes, and the choice of which non-essential hashes to persist is
    /// unconstrained. However, choosing not to persist non-essential hashes imposes computational
    /// overhead upon deserialization.
    async fn add_hash(
        &mut self,
        position: Position,
        height: u8,
        hash: Hash,
        essential: bool,
    ) -> Result<(), Self::Error>;

    /// Write a single commitment into storage.
    ///
    /// This should return an error if a commitment is already present at that location; no
    /// location's value should ever be overwritten.
    async fn add_commitment(
        &mut self,
        position: Position,
        commitment: StateCommitment,
    ) -> Result<(), Self::Error>;

    /// Delete every stored [`struct@Hash`] whose height is less than `below_height` and whose
    /// position is within the half-open [`Range`] of `positions`, as well as every [`Commitment`]
    /// whose position is within the range.
    async fn delete_range(
        &mut self,
        below_height: u8,
        positions: Range<Position>,
    ) -> Result<(), Self::Error>;

    /// Set the stored position of the tree.
    ///
    /// This should return an error if the position goes backwards.
    async fn set_position(&mut self, position: StoredPosition) -> Result<(), Self::Error>;

    /// Set the forgotten version of the tree.
    ///
    /// This should return an error if the version goes backwards.
    async fn set_forgotten(&mut self, forgotten: Forgotten) -> Result<(), Self::Error>;
}

/// A synchronous storage backend capable of reading stored [`struct@Hash`]es and [`Commitment`]s as
/// well as storing the current [`Position`].
pub trait Read {
    /// The error returned when something goes wrong in a request.
    type Error;

    /// The type of iterator returned when reading hashes from the database.
    type HashesIter<'a>: Iterator<Item = Result<(Position, u8, Hash), Self::Error>> + 'a
    where
        Self: 'a;

    /// The type of iterator returned when reading commitments from the database.
    type CommitmentsIter<'a>: Iterator<Item = Result<(Position, StateCommitment), Self::Error>> + 'a
    where
        Self: 'a;

    /// Fetch the current position stored.
    fn position(&mut self) -> Result<StoredPosition, Self::Error>;

    /// Fetch the current forgotten version.
    fn forgotten(&mut self) -> Result<Forgotten, Self::Error>;

    /// Fetch a specific hash at the given position and height, if it exists.
    fn hash(&mut self, position: Position, height: u8) -> Result<Option<Hash>, Self::Error>;

    /// Get the full list of all internal hashes stored, indexed by position and height.
    #[allow(clippy::type_complexity)]
    fn hashes(&mut self) -> Self::HashesIter<'_>;

    /// Fetch a specific commitment at the given position, if it exists.
    fn commitment(&mut self, position: Position) -> Result<Option<StateCommitment>, Self::Error>;

    /// Get the full list of all commitments stored, indexed by position.
    #[allow(clippy::type_complexity)]
    fn commitments(&mut self) -> Self::CommitmentsIter<'_>;
}

/// A synchronous storage backend capable of writing [`struct@Hash`]es and [`Commitment`]s, and
/// garbage-collecting those which have been forgotten.
pub trait Write: Read {
    /// Write a single hash into storage.
    ///
    /// Backends are only *required* to persist hashes marked as `essential`. They may choose to
    /// persist other hashes, and the choice of which non-essential hashes to persist is
    /// unconstrained. However, choosing not to persist non-essential hashes imposes computational
    /// overhead upon deserialization.
    fn add_hash(
        &mut self,
        position: Position,
        height: u8,
        hash: Hash,
        essential: bool,
    ) -> Result<(), Self::Error>;

    /// Write a single commitment into storage.
    ///
    /// This should return an error if a commitment is already present at that location; no
    /// location's value should ever be overwritten.
    fn add_commitment(
        &mut self,
        position: Position,
        commitment: StateCommitment,
    ) -> Result<(), Self::Error>;

    /// Delete every stored [`struct@Hash`] whose height is less than `below_height` and whose
    /// position is within the half-open [`Range`] of `positions`, as well as every [`Commitment`]
    /// whose position is within the range.
    fn delete_range(
        &mut self,
        below_height: u8,
        positions: Range<Position>,
    ) -> Result<(), Self::Error>;

    /// Set the stored position of the tree.
    ///
    /// This should return an error if the position goes backwards.
    fn set_position(&mut self, position: StoredPosition) -> Result<(), Self::Error>;

    /// Set the forgotten version of the tree.
    ///
    /// This should return an error if the version goes backwards.
    fn set_forgotten(&mut self, forgotten: Forgotten) -> Result<(), Self::Error>;
}

/// A single update to the underlying storage, as a data type.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Update {
    /// Set the position of the tree.
    SetPosition(StoredPosition),
    /// Set the forgotten version of the tree.
    SetForgotten(Forgotten),
    /// Add a commitment to the tree.
    StoreCommitment(StoreCommitment),
    /// Add a hash to the tree.
    StoreHash(StoreHash),
    /// Delete a range of hashes and commitments from the tree.
    DeleteRange(DeleteRange),
}

/// An update to the underlying storage that constitutes storing a single hash.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreHash {
    /// The position of the hash.
    pub position: Position,
    /// The height of the hash.
    pub height: u8,
    /// The hash itself.
    pub hash: Hash,
    /// Whether the hash is essential to store, or can be dropped at the discretion of the storage backend.
    pub essential: bool,
}

/// An update to the underlying storage that constitutes storing a single commitment.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreCommitment {
    /// The position of the commitment.
    pub position: Position,
    /// The commitment itself.
    pub commitment: StateCommitment,
}

/// An update to the underlying storage that constitutes deleting a range of hashes and commitments.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteRange {
    /// The height strictly below which hashes should be deleted.
    pub below_height: u8,
    /// The half-open range of positions within which hashes and commitments should be deleted.
    pub positions: Range<Position>,
}

/// A collection of updates to the underlying storage.
///
/// Note that this is both `FromIterator<Update>` and `Iterator<Item = Update>`, so you can
/// [`.collect()`](Iterator::collect) an `Iterator<Item = Update>` into this type, and you can also
/// iterate over its contained updates.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Updates {
    /// The new position to set, if any.
    pub set_position: Option<StoredPosition>,
    /// The new forgotten version to set, if any.
    pub set_forgotten: Option<Forgotten>,
    /// The new commitments to store.
    pub store_commitments: Vec<StoreCommitment>,
    /// The new hashes to store.
    pub store_hashes: Vec<StoreHash>,
    /// The ranges of hashes and commitments to delete.
    pub delete_ranges: Vec<DeleteRange>,
}

impl FromIterator<Update> for Updates {
    fn from_iter<I: IntoIterator<Item = Update>>(iter: I) -> Self {
        let mut updates = Updates::default();
        for update in iter {
            match update {
                Update::SetPosition(position) => updates.set_position = Some(position),
                Update::SetForgotten(forgotten) => updates.set_forgotten = Some(forgotten),
                Update::StoreCommitment(commitment) => updates.store_commitments.push(commitment),
                Update::StoreHash(hash) => updates.store_hashes.push(hash),
                Update::DeleteRange(range) => updates.delete_ranges.push(range),
            }
        }
        updates
    }
}

impl Iterator for Updates {
    type Item = Update;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some(position) = self.set_position.take() {
            return Some(Update::SetPosition(position));
        }
        if let Some(forgotten) = self.set_forgotten.take() {
            return Some(Update::SetForgotten(forgotten));
        }
        if let Some(commitment) = self.store_commitments.pop() {
            return Some(Update::StoreCommitment(commitment));
        }
        if let Some(hash) = self.store_hashes.pop() {
            return Some(Update::StoreHash(hash));
        }
        if let Some(range) = self.delete_ranges.pop() {
            return Some(Update::DeleteRange(range));
        }
        None
    }
}