penumbra_sdk_tct/storage/
deserialize.rs

1//! Non-incremental deserialization for the [`Tree`](crate::Tree).
2
3use futures::StreamExt;
4
5use crate::prelude::*;
6
7/// Deserialize a [`Tree`] from an asynchronous storage backend.
8pub async fn from_async_reader<R: AsyncRead>(reader: &mut R) -> Result<Tree, R::Error> {
9    let position = reader.position().await?;
10    let forgotten = reader.forgotten().await?;
11    let mut load_commitments = LoadCommitments::new(position, forgotten);
12    let mut commitments = reader.commitments();
13    while let Some((position, commitment)) = commitments.next().await.transpose()? {
14        load_commitments.insert(position, commitment);
15    }
16    drop(commitments);
17    let mut hashes = reader.hashes();
18    let mut load_hashes = load_commitments.load_hashes();
19    while let Some((position, height, hash)) = hashes.next().await.transpose()? {
20        load_hashes.insert(position, height, hash);
21    }
22    Ok(load_hashes.finish())
23}
24
25/// Deserialize a [`Tree`] from a synchronous storage backend.
26pub fn from_reader<R: Read>(reader: &mut R) -> Result<Tree, R::Error> {
27    let position = reader.position()?;
28    let forgotten = reader.forgotten()?;
29    let mut load_commitments = LoadCommitments::new(position, forgotten);
30    let mut commitments = reader.commitments();
31    while let Some((position, commitment)) = commitments.next().transpose()? {
32        load_commitments.insert(position, commitment);
33    }
34    drop(commitments);
35    let mut load_hashes = load_commitments.load_hashes();
36    let mut hashes = reader.hashes();
37    while let Some((position, height, hash)) = hashes.next().transpose()? {
38        load_hashes.insert(position, height, hash);
39    }
40    Ok(load_hashes.finish())
41}
42
43/// Builder for loading commitments to create a [`Tree`].
44///
45/// This does not check for internal consistency: inputs that are not derived from a serialization
46/// of the tree can lead to internal invariant violations.
47pub struct LoadCommitments {
48    inner: frontier::Top<frontier::Tier<frontier::Tier<frontier::Item>>>,
49    index: HashedMap<StateCommitment, index::within::Tree>,
50}
51
52impl LoadCommitments {
53    pub(crate) fn new(position: impl Into<StoredPosition>, forgotten: Forgotten) -> Self {
54        // Make an uninitialized tree with the correct position and forgotten version
55        let position = match position.into() {
56            StoredPosition::Position(position) => Some(position.into()),
57            StoredPosition::Full => None,
58        };
59        Self {
60            inner: OutOfOrder::uninitialized(position, forgotten),
61            index: HashedMap::default(),
62        }
63    }
64
65    /// Insert a commitment at a given position.
66    pub fn insert(&mut self, position: Position, commitment: StateCommitment) {
67        self.inner
68            .uninitialized_out_of_order_insert_commitment(position.into(), commitment);
69        self.index.insert(commitment, u64::from(position).into());
70    }
71
72    /// Start loading the hashes for the inside of the tree.
73    pub fn load_hashes(self) -> LoadHashes {
74        LoadHashes {
75            inner: self.inner,
76            index: self.index,
77        }
78    }
79}
80
81impl Extend<(Position, StateCommitment)> for LoadCommitments {
82    fn extend<T: IntoIterator<Item = (Position, StateCommitment)>>(&mut self, iter: T) {
83        for (position, commitment) in iter {
84            self.insert(position, commitment);
85        }
86    }
87}
88
89/// Builder for loading hashes to create a [`Tree`].
90pub struct LoadHashes {
91    inner: frontier::Top<frontier::Tier<frontier::Tier<frontier::Item>>>,
92    index: HashedMap<StateCommitment, index::within::Tree>,
93}
94
95impl LoadHashes {
96    /// Insert a hash at a given position and height.
97    pub fn insert(&mut self, position: Position, height: u8, hash: Hash) {
98        self.inner.unchecked_set_hash(position.into(), height, hash);
99    }
100
101    /// Finish loading the tree.
102    pub fn finish(mut self) -> Tree {
103        self.inner.finish_initialize();
104        Tree::unchecked_from_parts(self.index, self.inner)
105    }
106}
107
108impl Extend<(Position, u8, Hash)> for LoadHashes {
109    fn extend<T: IntoIterator<Item = (Position, u8, Hash)>>(&mut self, iter: T) {
110        for (position, height, hash) in iter {
111            self.insert(position, height, hash);
112        }
113    }
114}
115
116#[cfg(test)]
117mod test {
118    use super::*;
119    use proptest::{arbitrary::*, prelude::*};
120
121    proptest::proptest! {
122        #[test]
123        fn uninitialized_produces_correct_position_and_forgotten(init_position in prop::option::of(any::<Position>()), init_forgotten in any::<Forgotten>()) {
124            let tree: frontier::Top<frontier::Tier<frontier::Tier<frontier::Item>>> =
125                OutOfOrder::uninitialized(init_position.map(Into::into), init_forgotten);
126            assert_eq!(init_position, tree.position().map(Into::into));
127            assert_eq!(init_forgotten, tree.forgotten().unwrap());
128        }
129    }
130}