penumbra_sdk_tct/internal.rs
1//! The internal implementation of the tree, exposed here for documentation.
2//!
3//! This module and its submodules should not be expected to follow semantic versioning.
4//!
5//! ## Structure of Implementation
6//!
7//! The tiered commitment tree is not accessed directly _as a tree_; rather, the
8//! [`Tree`](crate::Tree), [`epoch::Builder`](crate::builder::epoch::Builder),
9//! [`epoch::Finalized`](crate::builder::epoch::Finalized),
10//! [`block::Builder`](crate::builder::block::Builder), and
11//! [`block::Finalized`](crate::builder::block::Finalized) structs from the top level of the crate
12//! contain a tree together with a hashmap which maps commitments to their corresponding index
13//! within the tree. This `internal` module and all its submodules concern themselves solely with
14//! the implementation of the tree itself, wherein commitments and their authentication paths are
15//! accessed by index. The surrounding pieces of the crate make use of the internal-facing API
16//! exposed by this module to implement an external API specific to the three-tiered
17//! tree/epoch/block commitment tree required by Penumbra.
18//!
19//! The tiered commitment tree has a very specific structure, and in this implementation we make
20//! strong Rust's type system to enforce that structure. In particular, we ensure that internal
21//! nodes are non-empty, and that leaves occur only at the bottom-most level of the tree. A
22//! consequence of this is that it is unrepresentable to build a tree which fails to be pruned of
23//! internal nodes holding no witnessed leaves, or whose leaves are of mismatched depth. A lesser
24//! benefit of this design is that recursive methods on trees can be monomorphized and therefore
25//! potentially inlined to the height of tree they are called upon, avoiding ever performing runtime
26//! checks about the tree structure (there no need to check whether a particular part of the tree is
27//! a leaf, internal node, etc., because this is statically known).
28//!
29//! This structural guarantee is achieved by defining these tree structures as _nested generic
30//! types_, and defining their methods as _recursive trait implementations_ (rather than as
31//! inductive non-generic enumeration types with recursive methods). These traits are all defined in
32//! [`interface`], but they are re-exported from [`frontier`] and [`complete`] as relevant.
33//!
34//! ## Tiers of Nodes of Leaves of Items: Frontier and Complete
35//!
36//! The primary exports of this module is the type [`frontier::Tier`]. It is in terms of this type
37//! that the [`Tree`](crate::Tree) and [`builder`](crate::builder) structs are defined: a
38//! [`Tree`](crate::Tree) is a `Top<Tier<Tier<Item>>>`, an epoch is a `Top<Tier<Item>>`, and a
39//! `Block` is a `Top<Item>` (each with a managed index of commitments alongside).
40//!
41//! Internally, a [`Tier`](frontier::Tier) is a quadtree where every internal node is annotated with
42//! the hash of its children, into which leaves (all at depth 8) are inserted in left to right
43//! order. The "frontier" represents the path from the root to the rightmost leaf, at every level
44//! containing any leftward siblings of a given frontier node, each of which is a [`complete`] tree
45//! which stores the finalized bulk of the items inserted into the tree. As new leaves are created,
46//! the frontier zig-zags rightwards, pushing finalized portions of itself into the leftward
47//! complete tree.
48//!
49//! All [`Tier`](frontier::Tier)s must contain at least one child, and may be either unfinalized or
50//! finalized; a [`Top`](frontier::Top) is like a tier, but it may be empty, and may not be
51//! finalized. Stacking a [`Top`](frontier::Top) on top of [`Tier`](frontier::Tier)s allows there to
52//! be a canonical representation for empty trees, and prevents the illegal state of a finalized
53//! top-level tree.
54//!
55//! As described above, a variety of recursively defined traits are used to define the behavior of
56//! trees. The [`Frontier`](frontier::Frontier) trait defines the operations possible on a frontier
57//! of a tree, while the [`Focus`](frontier::Focus) trait defines how to operate over the tip of a
58//! frontier, and the [`Forget`](frontier::Forget) trait defines how to remove witnessed leaves from
59//! a frontier.
60//!
61//! While the frontier changes on every inserted leaf, within the complete portion of the tree the
62//! only changes that occur are when leaves are forgotten and their containing nodes pruned. As a
63//! result, the traits exposed by the [`complete`] module are merely
64//! [`Complete`](complete::Complete), which is a marker trait used to ensure that every
65//! [`Frontier`](frontier::Frontier) type is paired with a unique corresponding type of complete
66//! tree, and the [`ForgetOwned`](complete::ForgetOwned) trait, which defines an equivalent to
67//! [`frontier::Forget`] that is applicable to the by-value usage pattern of complete trees.
68
69pub mod hash;
70pub mod height;
71pub mod interface;
72pub mod path;
73pub mod proof;
74pub mod three;
75
76mod insert;
77
78#[allow(missing_docs)]
79pub mod frontier {
80 //! [`Frontier`] things can be inserted into and updated, always representing the rightmost
81 //! (most recently inserted) element of a tree.
82 //!
83 //! In sketch: the structure of a single [`Tier`] contains eight [`Node`]s, the bottom-most of
84 //! which contains a [`Leaf`]. Alternatively, a tier can be a summarized [`Hash`] of what its
85 //! contents _would be_, and contain nothing at all besides this hash.
86 //!
87 //! At every level of a [`frontier::Tier`](Tier), the rightmost child of a
88 //! [`frontier::Node`](Node) is a [`frontier::Node`](Node); all leftward siblings are
89 //! [`complete::Node`](super::complete::Node)s. When the child of a [`frontier::Node`](Node)
90 //! becomes entirely full (all its possible leaves are inserted), it is transformed into a
91 //! [`complete::Node`](super::complete::Node) and appended to the list of complete siblings of
92 //! its parent, thus shifting the frontier rightwards.
93 //!
94 //! At any given time, the frontier is always fully materialized; no node within it is ever
95 //! summarized as a hash. It is at the point when a [`frontier::Node`](Node) becomes full and is
96 //! then finalized into a [`complete::Node`](super::complete::Node) that it is pruned, if it
97 //! contains no witnessed children.
98 //!
99 //! At the tip of the frontier, however deeply nested (perhaps within multiple [`Tier`]s), there
100 //! is a single [`Item`], which is either a [`Commitment`](crate::Commitment) or a hash of one.
101 //! Commitments can be inserted either with the intent to remember them, or with the intent to
102 //! immediately forget them; this determines whether the [`Item`] is a commitment or merely its
103 //! hash.
104 pub(crate) use super::interface::OutOfOrder;
105 #[doc(inline)]
106 pub use super::interface::{Focus, Forget, Frontier, Full, GetPosition};
107 pub(super) mod item;
108 pub(super) mod leaf;
109 pub(super) mod node;
110 pub(super) mod tier;
111 pub(super) mod top;
112 pub use super::insert::{Insert, InsertMut};
113 #[doc(inline)]
114 pub use {
115 item::Item,
116 leaf::Leaf,
117 node::Node,
118 tier::Tier,
119 top::{Top, TrackForgotten},
120 };
121}
122
123#[allow(missing_docs)]
124pub mod complete {
125 //! [`Complete`] things are sparse representations of only the data that was inserted using
126 //! [`Witness::Keep`](crate::Witness::Keep), with the data that was inserted using
127 //! [`Witness::Forget`](crate::Witness::Forget) being pruned eagerly.
128 //!
129 //! The structure of a single [`Tier`] contains eight levels of [`Node`]s, the bottom-most level
130 //! of which contains [`Leaf`]s. Alternatively, a tier can be a summarized [`Hash`] of what its
131 //! contents _would be_, and contain nothing at all besides this hash.
132 //!
133 //! In the internal levels of a [`complete::Tier`](Tier) are eight levels of
134 //! [`complete::Node`](Node)s, each of which may have between one and four children. If a node
135 //! does not have a given child, then it instead stores the hash that child used to have, when
136 //! it existed. Empty nodes (all of whose children would be hashes) are unrepresentable, and
137 //! instead their own hash is immediately stored in their parent node when their last child is
138 //! forgotten.
139 //!
140 //! At the bottom of the bottom-most tier (perhaps at the bottom of multiple [`Tier`]s), there
141 //! are [`Item`]s, each of which is merely a wrapper for a single
142 //! [`Commitment`](crate::Commitment).
143 pub(crate) use super::interface::OutOfOrderOwned;
144 #[doc(inline)]
145 pub use super::interface::{Complete, ForgetOwned};
146 pub(super) mod item;
147 pub(super) mod leaf;
148 pub(super) mod node;
149 pub(super) mod tier;
150 pub(super) mod top;
151 #[doc(inline)]
152 pub use {
153 item::Item,
154 leaf::Leaf,
155 node::Node,
156 tier::{Nested, Tier},
157 top::Top,
158 };
159}
160
161pub(crate) use interface::UncheckedSetHash;