penumbra_sdk_tct/internal/frontier/
tier.rs

1use std::fmt::Debug;
2
3use serde::{Deserialize, Serialize};
4
5use crate::prelude::*;
6
7/// A frontier of a tier of the tiered commitment tree, being an 8-deep quad-tree of items.
8#[derive(Derivative, Serialize, Deserialize)]
9#[derivative(
10    Debug(bound = "Item: Debug, Item::Complete: Debug"),
11    Clone(bound = "Item: Clone, Item::Complete: Clone")
12)]
13#[serde(bound(
14    serialize = "Item: Serialize, Item::Complete: Serialize",
15    deserialize = "Item: Deserialize<'de>, Item::Complete: Deserialize<'de>"
16))]
17pub struct Tier<Item: Focus + Clone>
18where
19    Item::Complete: Clone,
20{
21    inner: Inner<Item>,
22}
23
24type N<Focus> = frontier::Node<Focus>;
25type L<Item> = frontier::Leaf<Item>;
26
27/// An eight-deep frontier tree with the given item stored in each leaf.
28pub type Nested<Item> = N<N<N<N<N<N<N<N<L<Item>>>>>>>>>;
29// Count the levels:    1 2 3 4 5 6 7 8
30
31/// The inside of a frontier of a tier.
32#[derive(Derivative, Serialize, Deserialize)]
33#[derivative(
34    Debug(bound = "Item: Debug, Item::Complete: Debug"),
35    Clone(bound = "Item: Clone, Item::Complete: Clone")
36)]
37#[serde(bound(
38    serialize = "Item: Serialize, Item::Complete: Serialize + Clone",
39    deserialize = "Item: Deserialize<'de>, Item::Complete: Deserialize<'de>"
40))]
41pub enum Inner<Item: Focus + Clone>
42where
43    Item::Complete: Clone,
44{
45    /// A tree with at least one leaf.
46    Frontier(Box<Nested<Item>>),
47    /// A completed tree which has at least one witnessed child.
48    Complete(complete::Nested<Item::Complete>),
49    /// A tree which has been filled, but which witnessed no elements, so it is represented by a
50    /// single hash.
51    Hash(Hash),
52}
53
54impl<Item: Focus + Clone> From<Hash> for Tier<Item>
55where
56    Item::Complete: Clone,
57{
58    #[inline]
59    fn from(hash: Hash) -> Self {
60        Self {
61            inner: Inner::Hash(hash),
62        }
63    }
64}
65
66impl<Item: Focus + Clone> Tier<Item>
67where
68    Item::Complete: Clone,
69{
70    /// Create a new tier from a single item which will be its first element.
71    #[inline]
72    pub fn new(item: Item) -> Self {
73        Self {
74            inner: Inner::Frontier(Box::new(Nested::new(item))),
75        }
76    }
77
78    /// Insert an item or its hash into this frontier tier.
79    ///
80    /// If the tier is full, return the input item without inserting it.
81    #[inline]
82    pub fn insert(&mut self, item: Item) -> Result<(), Item> {
83        // Temporarily swap the inside for the empty hash (this will get put back immediately)
84        let inner = std::mem::replace(&mut self.inner, Inner::Hash(Hash::zero()));
85
86        let result;
87        (result, *self) = match (Self { inner }.insert_owned(item)) {
88            Ok(this) => (Ok(()), this),
89            Err((item, this)) => (Err(item), this),
90        };
91
92        result
93    }
94
95    #[inline]
96    fn insert_owned(self, item: Item) -> Result<Self, (Item, Self)> {
97        match self.inner {
98            // The tier is full or is a single hash, so return the item without inserting it
99            inner @ (Inner::Complete(_) | Inner::Hash(_)) => Err((item, Self { inner })),
100            // The tier is a frontier, so try inserting into it
101            Inner::Frontier(frontier) => {
102                if frontier.is_full() {
103                    // Don't even try inserting when we know it will fail: this means that there is *no
104                    // implicit finalization* of the frontier, even when it is full
105                    Err((
106                        item,
107                        Self {
108                            inner: Inner::Frontier(frontier),
109                        },
110                    ))
111                } else {
112                    // If it's not full, then insert the item into it (which we know will succeed)
113                    let inner =
114                        Inner::Frontier(Box::new(frontier.insert_owned(item).unwrap_or_else(
115                            |_| panic!("frontier is not full, so insert must succeed"),
116                        )));
117                    Ok(Self { inner })
118                }
119            }
120        }
121    }
122
123    /// Update the focused element of this tier using a function.
124    ///
125    /// If the tier is empty or finalized, the function is not executed, and this returns `None`.
126    #[inline]
127    pub fn update<T>(&mut self, f: impl FnOnce(&mut Item) -> T) -> Option<T> {
128        if let Inner::Frontier(frontier) = &mut self.inner {
129            frontier.update(f)
130        } else {
131            None
132        }
133    }
134
135    /// Get the focused element of this tier, if one exists.
136    ///
137    /// If the tier is empty or finalized, returns `None`.
138    #[inline]
139    pub fn focus(&self) -> Option<&Item> {
140        if let Inner::Frontier(frontier) = &self.inner {
141            frontier.focus()
142        } else {
143            None
144        }
145    }
146
147    /// Check whether this tier is full.
148    ///
149    /// If this returns `false`, then insertion will fail.
150    #[inline]
151    pub fn is_full(&self) -> bool {
152        match &self.inner {
153            Inner::Frontier(frontier) => frontier.is_full(),
154            Inner::Complete(_) | Inner::Hash(_) => true,
155        }
156    }
157
158    /// Finalize this tier so that it is internally marked as complete.
159    #[inline]
160    pub fn finalize(&mut self) -> bool {
161        let already_finalized = self.is_finalized();
162
163        // Temporarily replace the inside with the zero hash (it will get put back right away, this
164        // is just to satisfy the borrow checker)
165        let inner = std::mem::replace(&mut self.inner, Inner::Hash(Hash::zero()));
166
167        self.inner = match inner {
168            Inner::Frontier(frontier) => match frontier.finalize_owned() {
169                Insert::Hash(hash) => Inner::Hash(hash),
170                Insert::Keep(inner) => Inner::Complete(inner),
171            },
172            inner @ (Inner::Complete(_) | Inner::Hash(_)) => inner,
173        };
174
175        already_finalized
176    }
177
178    /// Check whether this tier has been finalized.
179    #[inline]
180    pub fn is_finalized(&self) -> bool {
181        match self.inner {
182            Inner::Frontier(_) => false,
183            Inner::Complete(_) | Inner::Hash(_) => true,
184        }
185    }
186}
187
188impl<Item: Focus + Clone> Height for Tier<Item>
189where
190    Item::Complete: Clone,
191{
192    type Height = <Nested<Item> as Height>::Height;
193}
194
195impl<Item: Focus + Clone> GetHash for Tier<Item>
196where
197    Item::Complete: Clone,
198{
199    #[inline]
200    fn hash(&self) -> Hash {
201        match &self.inner {
202            Inner::Frontier(frontier) => frontier.hash(),
203            Inner::Complete(complete) => complete.hash(),
204            Inner::Hash(hash) => *hash,
205        }
206    }
207
208    #[inline]
209    fn cached_hash(&self) -> Option<Hash> {
210        match &self.inner {
211            Inner::Frontier(frontier) => frontier.cached_hash(),
212            Inner::Complete(complete) => complete.cached_hash(),
213            Inner::Hash(hash) => Some(*hash),
214        }
215    }
216}
217
218impl<Item: Focus + Clone> Focus for Tier<Item>
219where
220    Item::Complete: Clone,
221{
222    type Complete = complete::Tier<Item::Complete>;
223
224    #[inline]
225    fn finalize_owned(self) -> Insert<Self::Complete> {
226        match self.inner {
227            Inner::Frontier(frontier) => match frontier.finalize_owned() {
228                Insert::Hash(hash) => Insert::Hash(hash),
229                Insert::Keep(inner) => Insert::Keep(complete::Tier { inner }),
230            },
231            Inner::Complete(inner) => Insert::Keep(complete::Tier { inner }),
232            Inner::Hash(hash) => Insert::Hash(hash),
233        }
234    }
235}
236
237impl<Item: Focus + Witness + Clone> Witness for Tier<Item>
238where
239    Item::Complete: Witness + Clone,
240{
241    #[inline]
242    fn witness(&self, index: impl Into<u64>) -> Option<(AuthPath<Self>, Hash)> {
243        match &self.inner {
244            Inner::Frontier(frontier) => frontier.witness(index),
245            Inner::Complete(complete) => complete.witness(index),
246            Inner::Hash(_) => None,
247        }
248    }
249}
250
251impl<Item: Focus + GetPosition + Clone> GetPosition for Tier<Item>
252where
253    Item::Complete: Clone,
254{
255    #[inline]
256    fn position(&self) -> Option<u64> {
257        match &self.inner {
258            Inner::Frontier(frontier) => frontier.position(),
259            Inner::Complete(_) | Inner::Hash(_) => None,
260        }
261    }
262}
263
264impl<Item: Focus + Forget + Clone> Forget for Tier<Item>
265where
266    Item::Complete: ForgetOwned + Clone,
267{
268    #[inline]
269    fn forget(&mut self, forgotten: Option<Forgotten>, index: impl Into<u64>) -> bool {
270        // Whether something was actually forgotten
271        let was_forgotten;
272
273        // Temporarily replace the inside with the zero hash (it will get put back right away, this
274        // is just to satisfy the borrow checker)
275        let inner = std::mem::replace(&mut self.inner, Inner::Hash(Hash::zero()));
276
277        (was_forgotten, self.inner) = match inner {
278            // If the tier is a frontier, try to forget from the frontier path, if it's not empty
279            Inner::Frontier(mut frontier) => {
280                (frontier.forget(forgotten, index), Inner::Frontier(frontier))
281            }
282            // If the tier is complete, forget from the complete tier and if it resulted in a hash,
283            // set the self to that hash
284            Inner::Complete(complete) => match complete.forget_owned(forgotten, index) {
285                (Insert::Keep(complete), forgotten) => (forgotten, Inner::Complete(complete)),
286                (Insert::Hash(hash), forgotten) => (forgotten, Inner::Hash(hash)),
287            },
288            // If the tier was just a hash, nothing to do
289            Inner::Hash(hash) => (false, Inner::Hash(hash)),
290        };
291
292        // Return whether something was actually forgotten
293        was_forgotten
294    }
295}
296
297impl<Item: Focus + Clone> From<complete::Tier<Item::Complete>> for Tier<Item>
298where
299    Item::Complete: Clone,
300{
301    fn from(complete: complete::Tier<Item::Complete>) -> Self {
302        Self {
303            inner: Inner::Complete(complete.inner),
304        }
305    }
306}
307
308impl<Item: Focus + Clone> From<complete::Top<Item::Complete>> for Tier<Item>
309where
310    Item::Complete: Clone,
311{
312    fn from(complete: complete::Top<Item::Complete>) -> Self {
313        Self {
314            inner: Inner::Complete(complete.inner),
315        }
316    }
317}
318
319impl<'tree, Item: Focus + GetPosition + Height + structure::Any<'tree> + Clone>
320    structure::Any<'tree> for Tier<Item>
321where
322    Item::Complete: structure::Any<'tree> + Clone,
323{
324    fn kind(&self) -> Kind {
325        Kind::Internal {
326            height: <Self as Height>::Height::HEIGHT,
327        }
328    }
329
330    fn forgotten(&self) -> Forgotten {
331        match &self.inner {
332            Inner::Frontier(frontier) => (&**frontier as &dyn structure::Any).forgotten(),
333            Inner::Complete(complete) => (complete as &dyn structure::Any).forgotten(),
334            Inner::Hash(_) => Forgotten::default(),
335        }
336    }
337
338    fn children(&'tree self) -> Vec<HashOrNode<'tree>> {
339        match &self.inner {
340            Inner::Frontier(frontier) => frontier.children(),
341            Inner::Complete(complete) => (complete as &dyn structure::Any).children(),
342            Inner::Hash(_hash) => vec![],
343        }
344    }
345}
346
347impl<Item: Focus + OutOfOrder + Clone> OutOfOrder for Tier<Item>
348where
349    Item::Complete: OutOfOrderOwned + Clone,
350{
351    fn uninitialized(position: Option<u64>, forgotten: Forgotten) -> Self {
352        // This tier is finalized if the position relative to its own height is 0 (because a
353        // frontier cannot represent a 0 position)
354        let is_finalized = if let Some(position) = position {
355            // This calculation checks whether the position "below" here is all zeros, which would
356            // mean that no frontier can be instantiated here, because any non-finalized tier would
357            // contribute at least 1 to the position, since tiers cannot be empty
358            position.trailing_zeros() >= (<Self as Height>::Height::HEIGHT as u32 * 2)
359        } else {
360            // If the position is `None` then this tier is not finalized, because the absolute last
361            // frontier ever to be produced in a full thing can't be finalized (proof by
362            // contradiction: a finalized tier has trailing zeros in its position, which means that
363            // more things could be inserted into it, a contradiction if we assumed that it
364            // represented the fullest possible tree)
365            false
366        };
367
368        Self {
369            inner: if is_finalized {
370                // We can't generate an uninitialized complete tier, so we use the uninitialized
371                // hash, which will be replaced with `Hash::one()` in the case when nothing is
372                // inserted into it, and with a complete tier in the case when something is inserted
373                // into it
374                Inner::Hash(Hash::uninitialized())
375            } else {
376                // In the case when we are a non-finalized tier, we recursively continue generating
377                // the frontier
378                Inner::Frontier(Box::new(Nested::uninitialized(position, forgotten)))
379            },
380        }
381    }
382
383    fn uninitialized_out_of_order_insert_commitment(
384        &mut self,
385        index: u64,
386        commitment: StateCommitment,
387    ) {
388        // We very temporarily swap the inner for the uninitialized hash, so we can manipulate it as
389        // an owned value, then we put the real thing immediately back
390        let inner = std::mem::replace(&mut self.inner, Inner::Hash(Hash::uninitialized()));
391        self.inner = match inner {
392            Inner::Frontier(mut frontier) => {
393                // Insert into the frontier and return it
394                frontier.uninitialized_out_of_order_insert_commitment(index, commitment);
395                Inner::Frontier(frontier)
396            }
397            Inner::Complete(complete) => {
398                // Insert into the complete tier and return it, using the `OutOfOrderOwned` impl for
399                // the inner nested complete structure
400                Inner::Complete(<Nested<Item> as Focus>::Complete::uninitialized_out_of_order_insert_commitment_owned(
401                    Insert::Keep(complete),
402                    index,
403                    commitment,
404                ))
405            }
406            Inner::Hash(hash) => {
407                // Do just as above, using the `OutOfOrderOwned` impl for the inner nested complete
408                // structure, except starting from the given hash
409                Inner::Complete(<Nested<Item> as Focus>::Complete::uninitialized_out_of_order_insert_commitment_owned(
410                    Insert::Hash(hash),
411                    index,
412                    commitment,
413                ))
414            }
415        };
416    }
417}
418
419impl<Item: Focus + UncheckedSetHash + Clone> UncheckedSetHash for Tier<Item>
420where
421    Item::Complete: UncheckedSetHash + Clone,
422{
423    fn unchecked_set_hash(&mut self, index: u64, height: u8, hash: Hash) {
424        match &mut self.inner {
425            Inner::Frontier(frontier) => frontier.unchecked_set_hash(index, height, hash),
426            Inner::Complete(complete) => complete.unchecked_set_hash(index, height, hash),
427            Inner::Hash(this_hash) => {
428                if height == Self::Height::HEIGHT {
429                    *this_hash = hash;
430                }
431            }
432        }
433    }
434
435    fn finish_initialize(&mut self) {
436        match &mut self.inner {
437            Inner::Frontier(frontier) => frontier.finish_initialize(),
438            Inner::Complete(complete) => complete.finish_initialize(),
439            Inner::Hash(hash) => {
440                if hash.is_uninitialized() {
441                    // A hashed tier is complete, so its hash should be `Hash::one()`
442                    *hash = Hash::one();
443                }
444            }
445        }
446    }
447}
448
449#[cfg(test)]
450mod test {
451    use super::*;
452
453    #[test]
454    fn position_advances_by_one() {
455        let mut tier: Tier<Item> = Tier::new(Hash::zero().into());
456        for expected_position in 1..=(u16::MAX as u64) {
457            assert_eq!(tier.position(), Some(expected_position));
458            tier.insert(Hash::zero().into()).unwrap();
459        }
460        assert_eq!(tier.position(), None);
461    }
462}