Skip to content

Commit 0ba01d6

Browse files
support split bagging in qmdb-current
1 parent df9c036 commit 0ba01d6

15 files changed

Lines changed: 1020 additions & 351 deletions

File tree

storage/src/merkle/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ pub use position::Position;
3333
#[cfg(test)]
3434
pub(crate) use proof::build_range_proof;
3535
pub use proof::Proof;
36+
#[cfg(feature = "std")]
37+
pub(crate) use proof::{build_range_collection_proof, range_collection_nodes};
3638
pub use read::Readable;
3739
use thiserror::Error;
3840

storage/src/merkle/proof.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,70 @@ impl<F: Family, D: Digest> Proof<F, D> {
738738
)
739739
.ok_or(ReconstructionError::InvalidProof)
740740
}
741+
742+
/// Authenticate the proven range without reconstructing the full generic Merkle root.
743+
///
744+
/// This consumes only the sibling digests needed to rebuild the proven range peaks, then returns
745+
/// those peak digests in `collected`. It deliberately does not consume peak-bagging witnesses
746+
/// such as prefix peaks, after peaks, or backward-fold suffix accumulators.
747+
///
748+
/// Current QMDB grafted proofs use this path because their final root is rebuilt by the wrapper
749+
/// from the collected range peaks plus grafted prefix/suffix witnesses. Including generic
750+
/// peak-bagging witnesses here would create proof bytes that the wrapper root ignores, making
751+
/// those bytes malleable.
752+
pub(crate) fn reconstruct_range_collecting<H, E>(
753+
&self,
754+
hasher: &H,
755+
elements: &[E],
756+
start_loc: Location<F>,
757+
collected: &mut Vec<(Position<F>, D)>,
758+
) -> Result<(), ReconstructionError>
759+
where
760+
H: Hasher<F, Digest = D>,
761+
E: AsRef<[u8]>,
762+
{
763+
if elements.is_empty() {
764+
return Err(ReconstructionError::MissingElements);
765+
}
766+
if !start_loc.is_valid_index() {
767+
return Err(ReconstructionError::InvalidStartLoc);
768+
}
769+
let end_loc = start_loc
770+
.checked_add(elements.len() as u64)
771+
.ok_or(ReconstructionError::InvalidEndLoc)?;
772+
if end_loc > self.leaves {
773+
return Err(ReconstructionError::InvalidEndLoc);
774+
}
775+
776+
let range = start_loc..end_loc;
777+
let bp = Blueprint::new_using_policy(
778+
self.leaves,
779+
self.inactive_peaks,
780+
Bagging::ForwardFold,
781+
range,
782+
)
783+
.map_err(|_| ReconstructionError::InvalidSize)?;
784+
785+
let mut sibling_cursor = 0usize;
786+
let mut elements_iter = elements.iter();
787+
for peak in &bp.range_peaks {
788+
let peak_digest = peak.reconstruct_digest(
789+
hasher,
790+
&bp.range,
791+
&mut elements_iter,
792+
&self.digests,
793+
&mut sibling_cursor,
794+
Some(collected),
795+
)?;
796+
collected.push((peak.pos, peak_digest));
797+
}
798+
799+
if elements_iter.next().is_some() || sibling_cursor != self.digests.len() {
800+
return Err(ReconstructionError::ExtraDigests);
801+
}
802+
803+
Ok(())
804+
}
741805
}
742806

743807
/// A perfect binary subtree within a peak, identified by its root position, height,
@@ -1222,6 +1286,52 @@ where
12221286
)
12231287
}
12241288

1289+
/// Return the node positions needed by [`build_range_collection_proof`].
1290+
#[cfg(feature = "std")]
1291+
pub(crate) fn range_collection_nodes<F: Family>(
1292+
leaves: Location<F>,
1293+
inactive_peaks: usize,
1294+
range: Range<Location<F>>,
1295+
) -> Result<Vec<Position<F>>, super::Error<F>> {
1296+
let bp = Blueprint::new_using_policy(leaves, inactive_peaks, Bagging::ForwardFold, range)?;
1297+
let mut fetch_nodes = Vec::new();
1298+
for peak in &bp.range_peaks {
1299+
peak.collect_siblings(&bp.range, &mut fetch_nodes);
1300+
}
1301+
Ok(fetch_nodes)
1302+
}
1303+
1304+
/// Build a proof containing only the sibling digests needed to authenticate the requested range.
1305+
///
1306+
/// The resulting proof cannot reconstruct a complete generic Merkle root on its own. It is intended
1307+
/// for wrappers that rebuild a custom root from separately supplied peak witnesses, such as current
1308+
/// QMDB grafted proofs.
1309+
#[cfg(feature = "std")]
1310+
pub(crate) fn build_range_collection_proof<F, D, E>(
1311+
leaves: Location<F>,
1312+
inactive_peaks: usize,
1313+
range: Range<Location<F>>,
1314+
get_node: impl Fn(Position<F>) -> Option<D>,
1315+
element_pruned: impl Fn(Position<F>) -> E,
1316+
) -> Result<Proof<F, D>, E>
1317+
where
1318+
F: Family,
1319+
D: Digest,
1320+
E: From<super::Error<F>>,
1321+
{
1322+
let fetch_nodes = range_collection_nodes(leaves, inactive_peaks, range)?;
1323+
let mut digests = Vec::with_capacity(fetch_nodes.len());
1324+
for pos in fetch_nodes {
1325+
digests.push(get_node(pos).ok_or_else(|| element_pruned(pos))?);
1326+
}
1327+
1328+
Ok(Proof {
1329+
leaves,
1330+
inactive_peaks,
1331+
digests,
1332+
})
1333+
}
1334+
12251335
/// Returns the positions of the minimal set of nodes whose digests are required to prove the
12261336
/// inclusion of the elements at the specified `locations`, using the provided root bagging.
12271337
#[cfg(any(feature = "std", test))]

storage/src/qmdb/current/batch.rs

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
mem::Mem, storage::Storage as MerkleStorage, Graftable, Location, Position, Readable,
1111
},
1212
qmdb::{
13+
self,
1314
any::{
1415
self,
1516
batch::{lookup_sorted, DiffEntry, FloorScan},
@@ -176,7 +177,7 @@ struct BatchStorageAdapter<
176177

177178
impl<
178179
'a,
179-
F: Graftable,
180+
F: Graftable + qmdb::RootSpec,
180181
D: Digest,
181182
R: Readable<Family = F, Digest = D, Error = merkle::Error<F>>,
182183
S: MerkleStorage<F, Digest = D>,
@@ -192,7 +193,7 @@ impl<
192193
}
193194

194195
impl<
195-
F: Graftable,
196+
F: Graftable + qmdb::RootSpec,
196197
D: Digest,
197198
R: Readable<Family = F, Digest = D, Error = merkle::Error<F>>,
198199
S: MerkleStorage<F, Digest = D>,
@@ -349,7 +350,7 @@ where
349350
// Unordered get + merkleize.
350351
impl<F, K, V, H, const N: usize> UnmerkleizedBatch<F, H, update::Unordered<K, V>, N>
351352
where
352-
F: Graftable,
353+
F: Graftable + qmdb::RootSpec,
353354
K: Key,
354355
V: ValueEncoding,
355356
H: Hasher,
@@ -412,7 +413,7 @@ where
412413
// Ordered get + merkleize.
413414
impl<F, K, V, H, const N: usize> UnmerkleizedBatch<F, H, update::Ordered<K, V>, N>
414415
where
415-
F: Graftable,
416+
F: Graftable + qmdb::RootSpec,
416417
K: Key,
417418
V: ValueEncoding,
418419
H: Hasher,
@@ -550,7 +551,7 @@ async fn compute_current_layer<F, E, U, C, I, H, const N: usize>(
550551
bitmap_parent: &BitmapBatch<N>,
551552
) -> Result<Arc<MerkleizedBatch<F, H::Digest, U, N>>, Error<F>>
552553
where
553-
F: Graftable,
554+
F: Graftable + qmdb::RootSpec,
554555
E: Context,
555556
U: update::Update + Send + Sync,
556557
C: Contiguous<Item = Operation<F, U>>,
@@ -674,6 +675,7 @@ where
674675
&bitmap_batch,
675676
&grafted_storage,
676677
partial,
678+
inner.new_inactivity_floor_loc,
677679
&ops_root,
678680
)
679681
.await?;
@@ -956,7 +958,7 @@ where
956958

957959
impl<F, E, C, I, H, U, const N: usize> super::db::Db<F, E, C, I, H, U, N>
958960
where
959-
F: Graftable,
961+
F: Graftable + qmdb::RootSpec,
960962
E: Context,
961963
U: update::Update + Send + Sync,
962964
C: Contiguous<Item = Operation<F, U>>,
@@ -1000,7 +1002,7 @@ mod trait_impls {
10001002
UnmerkleizedBatchTrait<CurrentDb<F, E, C, I, H, update::Unordered<K, V>, N>>
10011003
for UnmerkleizedBatch<F, H, update::Unordered<K, V>, N>
10021004
where
1003-
F: Graftable,
1005+
F: Graftable + qmdb::RootSpec,
10041006
K: Key,
10051007
V: ValueEncoding + 'static,
10061008
H: Hasher,
@@ -1020,20 +1022,29 @@ mod trait_impls {
10201022
Self::write(self, key, value)
10211023
}
10221024

1023-
fn merkleize(
1025+
async fn merkleize(
10241026
self,
10251027
db: &CurrentDb<F, E, C, I, H, update::Unordered<K, V>, N>,
10261028
metadata: Option<V::Value>,
1027-
) -> impl Future<Output = Result<Self::Merkleized, crate::qmdb::Error<F>>> {
1028-
self.merkleize(db, metadata)
1029+
) -> Result<Self::Merkleized, crate::qmdb::Error<F>> {
1030+
let Self {
1031+
inner,
1032+
grafted_parent,
1033+
bitmap_parent,
1034+
} = self;
1035+
let scan = BitmapScan::new(&bitmap_parent);
1036+
let inner = inner
1037+
.merkleize_with_floor_scan(&db.any, metadata, scan)
1038+
.await?;
1039+
compute_current_layer(inner, db, &grafted_parent, &bitmap_parent).await
10291040
}
10301041
}
10311042

10321043
impl<F, K, V, H, E, C, I, const N: usize>
10331044
UnmerkleizedBatchTrait<CurrentDb<F, E, C, I, H, update::Ordered<K, V>, N>>
10341045
for UnmerkleizedBatch<F, H, update::Ordered<K, V>, N>
10351046
where
1036-
F: Graftable,
1047+
F: Graftable + qmdb::RootSpec,
10371048
K: Key,
10381049
V: ValueEncoding + 'static,
10391050
H: Hasher,
@@ -1053,12 +1064,21 @@ mod trait_impls {
10531064
Self::write(self, key, value)
10541065
}
10551066

1056-
fn merkleize(
1067+
async fn merkleize(
10571068
self,
10581069
db: &CurrentDb<F, E, C, I, H, update::Ordered<K, V>, N>,
10591070
metadata: Option<V::Value>,
1060-
) -> impl Future<Output = Result<Self::Merkleized, crate::qmdb::Error<F>>> {
1061-
self.merkleize(db, metadata)
1071+
) -> Result<Self::Merkleized, crate::qmdb::Error<F>> {
1072+
let Self {
1073+
inner,
1074+
grafted_parent,
1075+
bitmap_parent,
1076+
} = self;
1077+
let scan = BitmapScan::new(&bitmap_parent);
1078+
let inner = inner
1079+
.merkleize_with_floor_scan(&db.any, metadata, scan)
1080+
.await?;
1081+
compute_current_layer(inner, db, &grafted_parent, &bitmap_parent).await
10621082
}
10631083
}
10641084

@@ -1077,7 +1097,7 @@ mod trait_impls {
10771097
impl<F, E, K, V, C, I, H, const N: usize> BatchableDb
10781098
for CurrentDb<F, E, C, I, H, update::Unordered<K, V>, N>
10791099
where
1080-
F: Graftable,
1100+
F: Graftable + qmdb::RootSpec,
10811101
E: Context,
10821102
K: Key,
10831103
V: ValueEncoding + 'static,
@@ -1094,22 +1114,22 @@ mod trait_impls {
10941114
type Batch = UnmerkleizedBatch<F, H, update::Unordered<K, V>, N>;
10951115

10961116
fn new_batch(&self) -> Self::Batch {
1097-
self.new_batch()
1117+
Self::new_batch(self)
10981118
}
10991119

11001120
fn apply_batch(
11011121
&mut self,
11021122
batch: Self::Merkleized,
11031123
) -> impl Future<Output = Result<core::ops::Range<Location<F>>, crate::qmdb::Error<F>>>
11041124
{
1105-
self.apply_batch(batch)
1125+
Self::apply_batch(self, batch)
11061126
}
11071127
}
11081128

11091129
impl<F, E, K, V, C, I, H, const N: usize> BatchableDb
11101130
for CurrentDb<F, E, C, I, H, update::Ordered<K, V>, N>
11111131
where
1112-
F: Graftable,
1132+
F: Graftable + qmdb::RootSpec,
11131133
E: Context,
11141134
K: Key,
11151135
V: ValueEncoding + 'static,
@@ -1126,15 +1146,15 @@ mod trait_impls {
11261146
type Batch = UnmerkleizedBatch<F, H, update::Ordered<K, V>, N>;
11271147

11281148
fn new_batch(&self) -> Self::Batch {
1129-
self.new_batch()
1149+
Self::new_batch(self)
11301150
}
11311151

11321152
fn apply_batch(
11331153
&mut self,
11341154
batch: Self::Merkleized,
11351155
) -> impl Future<Output = Result<core::ops::Range<Location<F>>, crate::qmdb::Error<F>>>
11361156
{
1137-
self.apply_batch(batch)
1157+
Self::apply_batch(self, batch)
11381158
}
11391159
}
11401160
}

0 commit comments

Comments
 (0)