Skip to content

Commit c69f9c8

Browse files
tweaks
1 parent a377c74 commit c69f9c8

5 files changed

Lines changed: 60 additions & 46 deletions

File tree

storage/src/merkle/proof.rs

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -629,23 +629,15 @@ impl<F: Family, D: Digest> Proof<F, D> {
629629
}
630630

631631
let range = start_loc..end_loc;
632-
// Bagging only governs the prefix/suffix accumulator layout, which this method
633-
// deliberately ignores; `range_peaks` and `fetch_nodes` are bagging-independent. Pass
634-
// ForwardFold as a placeholder.
635-
let bp = Blueprint::new(
636-
self.leaves,
637-
self.inactive_peaks,
638-
Bagging::ForwardFold,
639-
range,
640-
)
641-
.map_err(|_| ReconstructionError::InvalidSize)?;
632+
let peaks = range_peaks(self.leaves, self.inactive_peaks, &range)
633+
.map_err(|_| ReconstructionError::InvalidSize)?;
642634

643635
let mut sibling_cursor = 0usize;
644636
let mut elements_iter = elements.iter();
645-
for peak in &bp.range_peaks {
637+
for peak in &peaks {
646638
let peak_digest = peak.reconstruct_digest(
647639
hasher,
648-
&bp.range,
640+
&range,
649641
&mut elements_iter,
650642
&self.digests,
651643
&mut sibling_cursor,
@@ -846,6 +838,25 @@ impl<F: Family> Subtree<F> {
846838
}
847839
}
848840

841+
/// Return the peaks of a tree of `leaves` that overlap `range`, validating both the range and the
842+
/// declared `inactive_peaks` boundary.
843+
///
844+
/// The returned subtrees are bagging-independent: `Blueprint::new`'s prefix/suffix accumulator
845+
/// layout depends on bagging, but the per-peak partition of the proven range does not.
846+
///
847+
/// # Errors
848+
///
849+
/// See [`Blueprint::new`].
850+
pub(crate) fn range_peaks<F: Family>(
851+
leaves: Location<F>,
852+
inactive_peaks: usize,
853+
range: &Range<Location<F>>,
854+
) -> Result<Vec<Subtree<F>>, super::Error<F>> {
855+
// Bagging only affects `Blueprint`'s prefix/suffix bucketing; `range_peaks` is independent.
856+
Blueprint::new(leaves, inactive_peaks, Bagging::ForwardFold, range.clone())
857+
.map(|bp| bp.range_peaks)
858+
}
859+
849860
/// Blueprint for a range proof, separating fold-prefix peaks from nodes that must be fetched.
850861
pub(crate) struct Blueprint<F: Family> {
851862
/// Total number of leaves in the structure this blueprint was built for.
@@ -1135,34 +1146,34 @@ where
11351146
}
11361147

11371148
/// Return the node positions needed by [`build_range_collection_proof`].
1138-
///
1139-
/// Bagging only governs prefix/suffix accumulator layout, which this helper does not consult; the
1140-
/// `range_peaks` siblings it collects are bagging-independent. ForwardFold is passed as a
1141-
/// placeholder.
11421149
#[cfg(feature = "std")]
11431150
pub(crate) fn range_collection_nodes<F: Family>(
11441151
leaves: Location<F>,
11451152
inactive_peaks: usize,
11461153
range: Range<Location<F>>,
11471154
) -> Result<Vec<Position<F>>, super::Error<F>> {
1148-
let bp = Blueprint::new(leaves, inactive_peaks, Bagging::ForwardFold, range)?;
1155+
let peaks = range_peaks(leaves, inactive_peaks, &range)?;
11491156
let mut fetch_nodes = Vec::new();
1150-
for peak in &bp.range_peaks {
1151-
peak.collect_siblings(&bp.range, &mut fetch_nodes);
1157+
for peak in &peaks {
1158+
peak.collect_siblings(&range, &mut fetch_nodes);
11521159
}
11531160
Ok(fetch_nodes)
11541161
}
11551162

11561163
/// Build a proof containing only the sibling digests needed to authenticate the requested range.
11571164
///
1165+
/// `fetch_nodes` must be the slice returned by [`range_collection_nodes`] for the same `leaves` and
1166+
/// `inactive_peaks`; the caller passes it in so the Blueprint traversal isn't repeated when the
1167+
/// positions are already known (e.g., to drive concurrent fetching).
1168+
///
11581169
/// The resulting proof cannot reconstruct a complete generic Merkle root on its own. It is intended
11591170
/// for wrappers that rebuild a custom root from separately supplied peak witnesses, such as current
11601171
/// QMDB grafted proofs.
11611172
#[cfg(feature = "std")]
11621173
pub(crate) fn build_range_collection_proof<F, D, E>(
11631174
leaves: Location<F>,
11641175
inactive_peaks: usize,
1165-
range: Range<Location<F>>,
1176+
fetch_nodes: &[Position<F>],
11661177
get_node: impl Fn(Position<F>) -> Option<D>,
11671178
element_pruned: impl Fn(Position<F>) -> E,
11681179
) -> Result<Proof<F, D>, E>
@@ -1171,9 +1182,8 @@ where
11711182
D: Digest,
11721183
E: From<super::Error<F>>,
11731184
{
1174-
let fetch_nodes = range_collection_nodes(leaves, inactive_peaks, range)?;
11751185
let mut digests = Vec::with_capacity(fetch_nodes.len());
1176-
for pos in fetch_nodes {
1186+
for &pos in fetch_nodes {
11771187
digests.push(get_node(pos).ok_or_else(|| element_pruned(pos))?);
11781188
}
11791189

storage/src/qmdb/current/db.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,8 @@ pub(super) async fn compute_db_root<
784784
/// grafting height. Therefore, a single bitmap chunk may span across multiple smaller ops peaks.
785785
/// `grafting::grafted_root` intercepts the folding process to group these sub-grafting-height peaks,
786786
/// hash them together with their corresponding bitmap chunks, and then complete the final fold using
787-
/// the QMDB root spec. For MMR, this produces the exact same result as `hasher.root()`.
787+
/// the hasher's peak-bagging policy. For MMR, each complete chunk already appears as a single
788+
/// grafted peak, so no extra peak regrouping is needed.
788789
pub(super) async fn compute_grafted_root<
789790
F: merkle::Graftable,
790791
H: Hasher,

storage/src/qmdb/current/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@
223223
//!
224224
//! This combines two (or three) components into a single hash:
225225
//!
226-
//! - **QMDB ops root**: The root of the operations tree computed using the QMDB root spec for
226+
//! - **ops root**: The root of the operations tree computed using the QMDB root spec for
227227
//! the Merkle family (the inner [crate::qmdb::any] database's root). Used for state sync,
228228
//! where a client downloads operations and verifies each batch against this root using QMDB
229229
//! range proofs.
@@ -236,10 +236,10 @@
236236
//! usually incomplete. Its digest and bit count are folded into the canonical root hash.
237237
//!
238238
//! The canonical root is returned by [Db](db::Db)`::`[root()](db::Db::root).
239-
//! The QMDB ops root is returned by the `sync::Database` trait's `root()` method, since the sync
239+
//! The ops root is returned by the `sync::Database` trait's `root()` method, since the sync
240240
//! engine verifies batches against the ops root, not the canonical root.
241241
//!
242-
//! For state sync, the sync engine targets the QMDB ops root and verifies each batch against it.
242+
//! For state sync, the sync engine targets the ops root and verifies each batch against it.
243243
//! After sync, the bitmap and grafted tree are reconstructed deterministically from the
244244
//! operations, and the canonical root is computed. [proof::OpsRootWitness] can be used to validate
245245
//! that a particular ops root is committed by a trusted canonical root; the sync engine does not

storage/src/qmdb/current/proof.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -444,8 +444,9 @@ impl<F: Graftable, D: Digest> RangeProof<F, D> {
444444
let mut unfolded_prefix_peaks = Vec::new();
445445
let mut unfolded_suffix_peaks = Vec::new();
446446
let proof = if needs_grafted_peak_fold {
447-
let mut positions =
447+
let collection_positions =
448448
merkle::range_collection_nodes(leaves, inactive_peaks, range.clone())?;
449+
let mut positions = collection_positions.clone();
449450
positions.extend(layout.prefix.iter().map(|&(pos, _)| pos));
450451
positions.extend(layout.after.iter().map(|&(pos, _)| pos));
451452
positions.sort_unstable();
@@ -471,7 +472,7 @@ impl<F: Graftable, D: Digest> RangeProof<F, D> {
471472
let proof = merkle::build_range_collection_proof::<F, D, Error<F>>(
472473
leaves,
473474
inactive_peaks,
474-
range.clone(),
475+
&collection_positions,
475476
|pos| fetched.get(&pos).copied(),
476477
|pos| Error::from(merkle::Error::<F>::MissingNode(pos)),
477478
)?;

storage/src/qmdb/current/sync/mod.rs

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,20 @@
33
//! Contains implementation of [crate::qmdb::sync::Database] for all [Db](crate::qmdb::current::db::Db)
44
//! variants (ordered/unordered, fixed/variable).
55
//!
6-
//! The canonical root of a `current` database combines the QMDB ops root, grafted tree root, and
6+
//! The canonical root of a `current` database combines the ops root, grafted tree root, and
77
//! optional partial chunk into a single hash (see the [Root structure](super) section in the
8-
//! module documentation). The sync engine operates on the **QMDB ops root**, not the canonical
9-
//! root: it downloads operations and verifies each batch against the QMDB ops-root spec for the
10-
//! Merkle family.
11-
//! [crate::qmdb::current::proof::OpsRootWitness] can be used by callers that need to authenticate
12-
//! the synced ops root against a trusted canonical root; the sync engine does not perform this
13-
//! check itself.
8+
//! module documentation). The sync engine operates on the **ops root**, not the canonical root:
9+
//! it downloads operations and verifies each batch against the ops root using standard merkle
10+
//! range proofs (identical to `any` sync). [crate::qmdb::current::proof::OpsRootWitness] can be
11+
//! used by callers that need to authenticate the synced ops root against a trusted canonical root;
12+
//! the sync engine does not perform this check itself.
1413
//!
1514
//! After all operations are synced, the bitmap and grafted tree are reconstructed
1615
//! deterministically from the operations. The canonical root is then computed from the
17-
//! QMDB ops root, the reconstructed grafted tree root, and any partial chunk.
16+
//! ops root, the reconstructed grafted tree root, and any partial chunk.
1817
//!
1918
//! The [Database]`::`[root()](crate::qmdb::sync::Database::root)
20-
//! implementation returns the **QMDB ops root** (not the canonical root) because that is what the
19+
//! implementation returns the **ops root** (not the canonical root) because that is what the
2120
//! sync engine verifies against.
2221
//!
2322
//! For pruned databases (`range.start > 0`), grafted pinned nodes for the pruned region are
@@ -52,6 +51,7 @@ use crate::{
5251
},
5352
FixedValue, VariableValue,
5453
},
54+
bitmap::Shared,
5555
current::{
5656
db, grafting,
5757
ordered::{
@@ -137,17 +137,19 @@ where
137137
)
138138
.await?;
139139

140-
// Pre-build the activity-status bitmap with the pruned-chunk count derived from the sync
141-
// range, then hand it to `any::Db::init_from_log` which becomes the sole owner. Floor
142-
// division is intentional: chunks entirely below range.start are pruned. If range.start
143-
// is not chunk-aligned, the partial leading chunk is reconstructed by `init_from_log`,
144-
// which pads the gap between `pruned_chunks * CHUNK_SIZE_BITS` and the journal's
145-
// inactivity floor with inactive (false) bits.
140+
// Initialize bitmap with pruned chunks.
141+
//
142+
// Floor division is intentional: chunks entirely below range.start are pruned.
143+
// If range.start is not chunk-aligned, the partial leading chunk is reconstructed by
144+
// init_from_log, which pads the gap between `pruned_chunks * CHUNK_SIZE_BITS` and the
145+
// journal's inactivity floor with inactive (false) bits.
146146
let pruned_chunks = (*range.start() / BitMap::<N>::CHUNK_SIZE_BITS) as usize;
147147
let bitmap = BitMap::<N>::new_with_pruned_chunks(pruned_chunks)
148148
.map_err(|_| qmdb::Error::<F>::DataCorrupted("pruned chunks overflow"))?;
149-
let bitmap = Arc::new(qmdb::bitmap::Shared::<N>::new(bitmap));
149+
let bitmap = Arc::new(Shared::<N>::new(bitmap));
150150

151+
// Build any::Db, handing it the pre-allocated bitmap. `init_from_log` populates the bitmap
152+
// during replay.
151153
let any: AnyDb<F, E, J, I, H, U, N, S> = AnyDb::init_from_log(index, log, Some(bitmap)).await?;
152154

153155
// Fetch grafted pinned nodes from the ops tree. For each position the grafted family
@@ -333,7 +335,7 @@ macro_rules! impl_current_sync_database {
333335
true
334336
}
335337

336-
/// Returns the QMDB ops root (not the canonical root), since the sync engine verifies
338+
/// Returns the ops root (not the canonical root), since the sync engine verifies
337339
/// batches against the ops tree.
338340
fn root(&self) -> Self::Digest {
339341
self.any.root()
@@ -373,7 +375,7 @@ impl_current_sync_database!(
373375
// --- Resolver implementations ---
374376
//
375377
// The resolver for `current` databases serves ops-level proofs (not grafted proofs) from
376-
// the inner `any` db. The sync engine verifies each batch against the QMDB ops root.
378+
// the inner `any` db. The sync engine verifies each batch against the ops root.
377379

378380
macro_rules! impl_current_resolver {
379381
($db:ident, $op:ident, $val_bound:ident, $key_bound:path $(; $($where_extra:tt)+)?) => {

0 commit comments

Comments
 (0)