-
Notifications
You must be signed in to change notification settings - Fork 220
Expand file tree
/
Copy pathmod.rs
More file actions
342 lines (299 loc) · 13.8 KB
/
mod.rs
File metadata and controls
342 lines (299 loc) · 13.8 KB
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
330
331
332
333
334
335
336
337
338
339
340
341
342
//! Shared types for Merkle-family data structures (MMR, MMB).
//!
//! This module provides generic `Position<F>` and `Location<F>` types parameterized by a
//! [`Family`] marker trait. Each Merkle family (e.g. MMR, MMB) implements the trait with
//! its own constants and conversion formulas, while the shared arithmetic, codec, and comparison
//! logic lives here.
pub mod batch;
#[cfg(test)]
pub(crate) mod conformance;
pub mod hasher;
#[cfg(feature = "std")]
mod persisted;
#[cfg(feature = "std")]
pub use persisted::{compact, full};
mod location;
pub mod mem;
pub mod mmb;
pub mod mmr;
pub(super) mod path;
mod position;
mod proof;
mod read;
#[cfg(feature = "std")]
pub mod storage;
#[cfg(feature = "std")]
pub mod verification;
use alloc::vec::Vec;
use core::fmt::Debug;
pub use location::{Location, LocationRangeExt};
pub use position::Position;
#[cfg(test)]
pub(crate) use proof::build_range_proof;
pub use proof::Proof;
#[cfg(feature = "std")]
pub(crate) use proof::{build_range_collection_proof, range_collection_nodes};
pub use read::Readable;
use thiserror::Error;
/// Defines the strategy used to fold peaks into the final root digest.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Bagging {
/// Peaks are bagged forward from oldest to newest, placing the smallest peak at the top.
ForwardFold,
/// Peaks are bagged backward from newest to oldest, placing the largest peak at the top.
BackwardFold,
}
/// Marker trait for Merkle-family data structures.
///
/// Provides the per-family constants and conversion functions that differentiate
/// MMR from MMB (or other future Merkle structures). Families capture structural topology
/// only; bagging is owned by the [`hasher::Hasher`] instance and supplied by the consumer.
pub trait Family: Copy + Clone + Debug + Default + Send + Sync + 'static {
/// Maximum valid node count / size.
const MAX_NODES: Position<Self>;
/// Maximum valid leaf count.
const MAX_LEAVES: Location<Self>;
/// Convert a leaf location to its node position, or equivalently, convert a leaf count to the
/// corresponding total node count (size).
///
/// The public, guaranteed domain is `loc <= MAX_LEAVES`. Some implementations may also accept
/// slightly larger temporary values for internal probing (for example, when checking the next
/// size boundary), but callers must not rely on that behavior.
fn location_to_position(loc: Location<Self>) -> Position<Self>;
/// Convert a node position to its leaf location, or `None` if the position is not a leaf.
/// Equivalently, convert a total node count (size) to the corresponding leaf count, returning
/// `None` if the size is not valid.
///
/// The caller guarantees `pos <= MAX_NODES`.
fn position_to_location(pos: Position<Self>) -> Option<Location<Self>>;
/// Whether `size` is a valid tree size for this Merkle structure.
fn is_valid_size(size: Position<Self>) -> bool;
/// Returns the largest valid size that is no greater than `size`.
fn to_nearest_size(size: Position<Self>) -> Position<Self>;
/// Return the peaks of a structure with the given `size` as `(position, height)` pairs
/// in canonical oldest-to-newest order (suitable for
/// [`Hasher::root`](crate::merkle::hasher::Hasher::root)).
fn peaks(size: Position<Self>) -> impl Iterator<Item = (Position<Self>, u32)> + Send;
/// Count the number of oldest peaks that are entirely inactive.
///
/// A peak is considered entirely inactive if all of its leaves strictly precede the
/// `inactivity_floor`. These peaks can be safely bagged forward into the `grafted_root`.
fn inactive_peaks(size: Position<Self>, inactivity_floor: Location<Self>) -> usize {
let mut inactive_count = 0;
let mut leaf_capacity_sum = 0u64;
for (_, height) in Self::peaks(size) {
let capacity = 1u64.checked_shl(height).expect("height excessively large");
leaf_capacity_sum = leaf_capacity_sum
.checked_add(capacity)
.expect("capacity overflow");
if leaf_capacity_sum <= *inactivity_floor {
inactive_count += 1;
} else {
break;
}
}
inactive_count
}
/// Compute positions of nodes that must be pinned when pruning to `prune_loc`.
///
/// Pinned nodes are the minimal set of pruned digests required to continue growing the
/// structure and recomputing its root after pruning. The default implementation returns the
/// peaks of the sub-structure at `prune_loc`, which is sufficient for both root computation and
/// re-merkleization of retained leaves. Implementations may override this if their family
/// requires a different canonical pinned-node set for the pruning boundary.
///
/// # Panics
///
/// Implementations panic if `prune_loc` is invalid (i.e., exceeds
/// [`MAX_LEAVES`](Self::MAX_LEAVES)). Callers must validate inputs before calling.
fn nodes_to_pin(prune_loc: Location<Self>) -> impl Iterator<Item = Position<Self>> + Send {
let prune_pos = Self::location_to_position(prune_loc);
Self::peaks(prune_pos)
.filter(move |&(pos, _)| pos < prune_pos)
.map(|(pos, _)| pos)
.collect::<Vec<_>>()
.into_iter()
}
/// Return the positions of the left and right children of the node at `pos` with the
/// given `height`. The caller guarantees `height > 0` (leaves have no children).
fn children(pos: Position<Self>, height: u32) -> (Position<Self>, Position<Self>);
/// Return the heights of the internal nodes that lie between `size_for(N)` and
/// `size_for(N+1)`, where `N` is the given leaf count. These are the nodes created
/// when the `N`-th leaf is appended.
fn parent_heights(leaves: Location<Self>) -> impl Iterator<Item = u32>;
/// Return the height of the node at `pos`.
///
/// # Panics
///
/// Implementations may panic if `pos` does not correspond to a node that exists in some valid
/// instance of this family (i.e., it is not a position that would appear in a structure of any
/// size).
fn pos_to_height(pos: Position<Self>) -> u32;
}
/// Extension of [`Family`] with methods needed for grafting bitmap chunks onto a Merkle structure.
/// Grafting combines an activity bitmap with an ops Merkle structure by hashing bitmap chunks
/// together with ops subtree roots. These methods provide the coordinate conversions and
/// chunk-to-peak mappings required by that process.
pub trait Graftable: Family {
/// Return the nodes that collectively cover the leaf range of a bitmap chunk in a structure of
/// the given `size`.
///
/// A chunk at index `chunk_idx` with grafting height `grafting_height` covers leaves
/// `[chunk_idx << grafting_height, (chunk_idx + 1) << grafting_height)`. The returned nodes
/// partition that range: each node's leaf range is entirely within the chunk, and together they
/// cover it exactly.
///
/// Results are returned in oldest-to-newest (left-to-right) order.
///
/// # Panics
///
/// Panics if `size` is not a valid size or if the chunk's leaf range exceeds the structure's
/// leaf count.
fn chunk_peaks(
size: Position<Self>,
chunk_idx: u64,
grafting_height: u32,
) -> impl Iterator<Item = (Position<Self>, u32)> + Send;
/// Return the deterministic position of the node at `height` whose leftmost leaf is at
/// `leaf_start`.
///
/// For some families, this position corresponds to a node that physically exists in any
/// structure containing those leaves. For others (e.g. MMB with delayed merging), it may be a
/// "virtual" position that no actual node occupies, but is still deterministic and unique for
/// the given leaf range and height.
///
/// Used by grafting to map grafted-structure positions to ops-structure positions for domain
/// separation in hash pre-images.
///
/// # Panics
///
/// Panics if `height` is excessively large (e.g., `>= 63`), or if the resulting position
/// computation overflows the bounds of the underlying numeric types.
fn subtree_root_position(leaf_start: Location<Self>, height: u32) -> Position<Self>;
/// Return the location of the leftmost leaf covered by the node at `pos` with `height`. For a
/// leaf (height 0), returns its own location.
///
/// # Panics
///
/// Panics if `height` is excessively large (e.g., `>= 63`), or if an invalid combination of
/// `pos` and `height` results in arithmetic underflow/overflow.
fn leftmost_leaf(pos: Position<Self>, height: u32) -> Location<Self>;
/// Return the minimum leaf count at which the node at `pos` with `height` exists in the
/// structure.
///
/// For families without delayed merging (e.g. MMR), a node exists as soon as all leaves
/// in its span have been appended. For families with delayed merging (e.g. MMB), the
/// node is created some number of leaf insertions _after_ its last leaf, so the birth
/// size is larger. The MMB override accounts for this delay.
///
/// This is used by the grafted-tree pruning logic to determine when a chunk-pair's
/// parent has been born in the ops tree, which controls when it is safe to prune the
/// pair's individual grafted leaves.
///
/// # Panics
///
/// Panics if `height` is excessively large (e.g., `>= 63`), or if arithmetic overflows.
fn peak_birth_size(pos: Position<Self>, height: u32) -> u64 {
let leftmost = *Self::leftmost_leaf(pos, height);
let width = 1u64.checked_shl(height).expect("height excessively large");
leftmost.checked_add(width).expect("birth size overflow")
}
}
/// Errors that can occur when interacting with a Merkle-family data structure.
#[derive(Debug, Error)]
pub enum Error<F: Family> {
/// The position does not correspond to a leaf node.
#[error("{0} is not a leaf")]
NonLeaf(Position<F>),
/// The position exceeds the valid range.
#[error("{0} > MAX_NODES")]
PositionOverflow(Position<F>),
/// The location exceeds the valid range.
#[error("{0} > MAX_LEAVES")]
LocationOverflow(Location<F>),
/// The range is empty but must contain at least one element.
#[error("range is empty")]
Empty,
/// The end of a range is out of bounds.
#[error("range end out of bounds: {0}")]
RangeOutOfBounds(Location<F>),
/// The requested size is invalid.
#[error("invalid size: {0}")]
InvalidSize(u64),
/// A requested leaf location exceeds the current leaf count.
#[error("leaf location out of bounds: {0}")]
LeafOutOfBounds(Location<F>),
/// A required node was not available (e.g. pruned).
#[error("element pruned: {0}")]
ElementPruned(Position<F>),
/// A required digest was compressed into a synthetic accumulator (e.g. a backward-fold
/// suffix accumulator) when the source proof was constructed, so it is not individually
/// available. Unlike [`Error::ElementPruned`], the digest still exists in the source
/// structure; the caller can recover by supplying it through an explicit witness slice.
#[error("digest hidden behind synthetic accumulator: {0}")]
CompressedDigest(Position<F>),
/// The provided pinned node list does not match the expected pruning boundary.
#[error("invalid pinned nodes")]
InvalidPinnedNodes,
/// Structure has diverged incompatibly from the batch's ancestor chain.
#[error("stale batch: base size {expected}, current size {actual}")]
StaleBatch {
/// The base size when the batch chain was forked.
expected: Position<F>,
/// The current structure size.
actual: Position<F>,
},
/// An ancestor batch was dropped before this batch was applied, causing
/// data loss. All ancestors must be kept alive until descendants are applied.
#[error("ancestor dropped: expected size {expected}, actual size {actual}")]
AncestorDropped {
/// The expected size after applying all ancestors + this batch.
expected: Position<F>,
/// The actual size (less than expected due to missing ancestor data).
actual: Position<F>,
},
/// The proof is invalid.
#[error("invalid proof")]
InvalidProof,
/// The requested inactive peak count cannot be represented by the current peak list.
#[error("inactive peak count {requested} exceeds peak count {peaks}")]
InvalidInactivePeaks {
/// Requested inactive peak count.
requested: usize,
/// Number of available peaks.
peaks: usize,
},
/// The root does not match the computed root.
#[error("root mismatch")]
RootMismatch,
/// A required digest is missing.
#[error("missing digest: {0}")]
MissingDigest(Position<F>),
/// A metadata error occurred.
#[cfg(feature = "std")]
#[error("metadata error: {0}")]
Metadata(#[from] crate::metadata::Error),
/// A journal error occurred.
#[cfg(feature = "std")]
#[error("journal error: {0}")]
Journal(#[from] crate::journal::Error),
/// A runtime error occurred.
#[cfg(feature = "std")]
#[error("runtime error: {0}")]
Runtime(#[from] commonware_runtime::Error),
/// A required node is missing.
#[error("missing node: {0}")]
MissingNode(Position<F>),
/// Data is corrupted.
#[error("data corrupted: {0}")]
DataCorrupted(&'static str),
/// A required grafted leaf digest is missing.
#[error("missing grafted leaf digest: {0}")]
MissingGraftedLeaf(Position<F>),
/// Bit offset is out of bounds.
#[error("bit offset {0} out of bounds (size: {1})")]
BitOutOfBounds(u64, u64),
/// Rewind was attempted but no prior committed state is available.
#[error("rewind beyond history")]
RewindBeyondHistory,
}