penumbra_sdk_tct/internal/frontier/
top.rs

1use std::fmt::Debug;
2
3use serde::{Deserialize, Serialize};
4
5use crate::prelude::*;
6
7use frontier::tier::Nested;
8
9/// The frontier of the top level of some part of the commitment tree, which may be empty, but may
10/// not be finalized or hashed.
11#[derive(Derivative, Serialize, Deserialize)]
12#[derivative(
13    Debug(bound = "Item: Debug, Item::Complete: Debug"),
14    Clone(bound = "Item: Clone, Item::Complete: Clone")
15)]
16#[serde(bound(
17    serialize = "Item: Serialize, Item::Complete: Serialize",
18    deserialize = "Item: Deserialize<'de>, Item::Complete: Deserialize<'de>"
19))]
20pub struct Top<Item: Focus + Clone>
21where
22    Item::Complete: Clone,
23{
24    track_forgotten: TrackForgotten,
25    inner: Option<Nested<Item>>,
26}
27
28/// Whether or not to track forgotten elements of the tree.
29///
30/// This is set to `Yes` for trees, but to `No` for epoch and block builders, because when they are
31/// inserted all at once, there would be no meaning to their internal forgotten versions, and the
32/// tree wouldn't have known about any elements that were forgotten before the builder was inserted,
33/// so it doesn't need to track them.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
35pub enum TrackForgotten {
36    /// Do keep track of what things are forgotten.
37    Yes,
38    /// Do not keep track of what things are forgotten.
39    No,
40}
41
42impl<Item: Focus + Clone> Top<Item>
43where
44    Item::Complete: Clone,
45{
46    /// Create a new top-level frontier tier.
47    #[allow(unused)]
48    pub fn new(track_forgotten: TrackForgotten) -> Self {
49        Self {
50            track_forgotten,
51            inner: None,
52        }
53    }
54
55    /// Insert an item or its hash into this frontier tier.
56    ///
57    /// If the tier is full, return the input item without inserting it.
58    #[inline]
59    pub fn insert(&mut self, item: Item) -> Result<(), Item> {
60        // Temporarily replace the inside with `None` (it will get put back right away, this is just
61        // to satisfy the borrow checker)
62        let inner = std::mem::take(&mut self.inner);
63
64        let (result, inner) = if let Some(inner) = inner {
65            if inner.is_full() {
66                // Don't even try inserting when we know it will fail: this means that there is *no
67                // implicit finalization* of the frontier, even when it is full
68                (Err(item), inner)
69            } else {
70                // If it's not full, then insert the item into it (which we know will succeed)
71                let inner = inner
72                    .insert_owned(item)
73                    .unwrap_or_else(|_| panic!("frontier is not full, so insert must succeed"));
74                (Ok(()), inner)
75            }
76        } else {
77            // If the tier was empty, create a new frontier containing only the inserted item
78            let inner = Nested::new(item);
79            (Ok(()), inner)
80        };
81
82        // Put the inner back
83        self.inner = Some(inner);
84
85        result
86    }
87
88    /// Forgot the commitment at the given index.
89    ///
90    /// This isn't an implementation of [`Forget`] because unlike [`Forget`], this doesn't require
91    /// an input forgotten version, since it calculates it based on the forgotten versions at this
92    /// top level.
93    #[inline]
94    pub fn forget(&mut self, index: impl Into<u64>) -> bool
95    where
96        Item: Forget,
97        Item::Complete: ForgetOwned,
98    {
99        let forgotten = self.forgotten();
100
101        if let Some(ref mut inner) = self.inner {
102            inner.forget(forgotten, index)
103        } else {
104            false
105        }
106    }
107
108    /// Count the number of times something has been forgotten from this tree.
109    #[inline]
110    pub fn forgotten(&self) -> Option<Forgotten> {
111        if let TrackForgotten::Yes = self.track_forgotten {
112            Some(
113                self.inner
114                    .iter()
115                    .flat_map(|inner| inner.forgotten().iter().copied())
116                    .max()
117                    .unwrap_or_default(),
118            )
119        } else {
120            None
121        }
122    }
123
124    /// Update the currently focused `Item` (i.e. the most-recently-[`insert`](Self::insert)ed one),
125    /// returning the result of the function.
126    ///
127    /// If this top-level tier is empty or the most recently inserted item is a hash, returns
128    /// `None`.
129    #[inline]
130    pub fn update<T>(&mut self, f: impl FnOnce(&mut Item) -> T) -> Option<T> {
131        self.inner.as_mut().and_then(|inner| inner.update(f))
132    }
133
134    /// Get a reference to the focused `Insert<Item>`, if there is one.
135    ///
136    /// If this top-level tier is empty or the focus is a hash, returns `None`.
137    #[inline]
138    pub fn focus(&self) -> Option<&Item> {
139        if let Some(ref inner) = self.inner {
140            inner.focus()
141        } else {
142            None
143        }
144    }
145
146    /// Finalize the top tier into either a summary root hash or a complete tier.
147    #[inline]
148    pub fn finalize(self) -> Insert<complete::Top<Item::Complete>> {
149        if let Some(inner) = self.inner {
150            inner.finalize_owned().map(|inner| complete::Top { inner })
151        } else {
152            // The hash of an empty top-level tier is 1
153            Insert::Hash(Hash::one())
154        }
155    }
156
157    /// Check whether this top-level tier is full.
158    #[inline]
159    pub fn is_full(&self) -> bool {
160        if let Some(ref inner) = self.inner {
161            inner.is_full()
162        } else {
163            false
164        }
165    }
166
167    /// Check whether this top-level tier is empty.
168    #[inline]
169    pub fn is_empty(&self) -> bool {
170        self.inner.is_none()
171    }
172}
173
174impl<Item: Focus + Clone> Height for Top<Item>
175where
176    Item::Complete: Clone,
177{
178    type Height = <Nested<Item> as Height>::Height;
179}
180
181impl<Item: Focus + GetPosition + Clone> GetPosition for Top<Item>
182where
183    Item::Complete: Clone,
184{
185    #[inline]
186    fn position(&self) -> Option<u64> {
187        if let Some(ref frontier) = self.inner {
188            frontier.position()
189        } else {
190            Some(0)
191        }
192    }
193}
194
195impl<Item: Focus + Clone> GetHash for Top<Item>
196where
197    Item::Complete: Clone,
198{
199    #[inline]
200    fn hash(&self) -> Hash {
201        if let Some(ref inner) = self.inner {
202            inner.hash()
203        } else {
204            Hash::zero()
205        }
206    }
207
208    #[inline]
209    fn cached_hash(&self) -> Option<Hash> {
210        if let Some(ref inner) = self.inner {
211            inner.cached_hash()
212        } else {
213            Some(Hash::zero())
214        }
215    }
216}
217
218impl<Item: Focus + Witness + Clone> Witness for Top<Item>
219where
220    Item::Complete: Witness + Clone,
221{
222    fn witness(&self, index: impl Into<u64>) -> Option<(AuthPath<Self>, Hash)> {
223        if let Some(ref inner) = self.inner {
224            inner.witness(index)
225        } else {
226            None
227        }
228    }
229}
230
231impl<'tree, Item: Focus + GetPosition + Height + structure::Any<'tree> + Clone>
232    structure::Any<'tree> for Top<Item>
233where
234    Item::Complete: structure::Any<'tree> + Clone,
235{
236    fn kind(&self) -> Kind {
237        Kind::Internal {
238            height: <Self as Height>::Height::HEIGHT,
239        }
240    }
241
242    fn forgotten(&self) -> Forgotten {
243        self.forgotten().unwrap_or_default()
244    }
245
246    fn children(&'tree self) -> Vec<HashOrNode<'tree>> {
247        self.inner
248            .as_ref()
249            .map(structure::Any::children)
250            .unwrap_or_default()
251    }
252}
253
254impl<Item: Focus + Height + OutOfOrder + Clone> OutOfOrder for Top<Item>
255where
256    Item::Complete: OutOfOrderOwned + Clone,
257{
258    fn uninitialized(position: Option<u64>, forgotten: Forgotten) -> Self {
259        let inner = if position == Some(0) {
260            // If the position is zero, there's no frontier to manifest
261            None
262        } else {
263            // Otherwise, create a frontier
264            Some(Nested::uninitialized(position, forgotten))
265        };
266
267        Self {
268            inner,
269            // Track forgotten things by default (we only deserialize entire full trees, which
270            // always have this flipped on)
271            track_forgotten: TrackForgotten::Yes,
272        }
273    }
274
275    fn uninitialized_out_of_order_insert_commitment(
276        &mut self,
277        index: u64,
278        commitment: StateCommitment,
279    ) {
280        if let Some(ref mut inner) = self.inner {
281            inner.uninitialized_out_of_order_insert_commitment(index, commitment);
282        }
283    }
284}
285
286impl<Item: Focus + Height + UncheckedSetHash + Clone> UncheckedSetHash for Top<Item>
287where
288    Item::Complete: UncheckedSetHash + Clone,
289{
290    fn unchecked_set_hash(&mut self, index: u64, height: u8, hash: Hash) {
291        if let Some(ref mut inner) = self.inner {
292            inner.unchecked_set_hash(index, height, hash);
293        }
294    }
295
296    fn finish_initialize(&mut self) {
297        if let Some(ref mut inner) = self.inner {
298            inner.finish_initialize();
299        }
300    }
301}
302
303#[cfg(test)]
304mod test {
305    use super::*;
306
307    #[test]
308    fn position_advances_by_one() {
309        let mut top: Top<Item> = Top::new(TrackForgotten::No);
310        for expected_position in 0..=(u16::MAX as u64) {
311            assert_eq!(top.position(), Some(expected_position));
312            top.insert(Hash::zero().into()).unwrap();
313        }
314        assert_eq!(top.position(), None);
315    }
316}