Skip to content

[storage/qmdb/current] support split (pyramid) bagging in qmdb-current#3693

Merged
patrick-ogrady merged 13 commits into
mainfrom
pyramid-grafting
May 5, 2026
Merged

[storage/qmdb/current] support split (pyramid) bagging in qmdb-current#3693
patrick-ogrady merged 13 commits into
mainfrom
pyramid-grafting

Conversation

@roberto-bayardo
Copy link
Copy Markdown
Collaborator

@roberto-bayardo roberto-bayardo commented Apr 29, 2026

Summary

  • Switch QMDB operation roots to pyramid bagging (split + backwards-fold active region).
  • Update Current grafted root and range-proof reconstruction so MMR/MMB proofs handle inactive prefixes, grafted peak regrouping, and partial chunks consistently.
  • Align Current sync, compact sync verification, conformance fixtures, and fuzz tampering coverage with the new root semantics.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 29, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
commonware-mcp 3a36c86 May 05 2026, 07:55 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 29, 2026

Deploying monorepo with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3a36c86
Status: ✅  Deploy successful!
Preview URL: https://959a9de5.monorepo-eu0.pages.dev
Branch Preview URL: https://pyramid-grafting.monorepo-eu0.pages.dev

View logs

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for split-root (inactive prefix) policies and pyramid/backward bagging in qmdb-current proof generation/verification, aligning current DB sync/roots with per-family RootSpec behavior.

Changes:

  • Update current DBs and test harnesses to require qmdb::RootSpec on Merkle families and propagate inactivity_floor into grafted/root computations.
  • Rework current range proof generation/verification to support backward-bagged proofs by adding explicit suffix witnesses and a “range-collection-only” generic Merkle proof path.
  • Refactor grafted-root folding to compute roots using RootSpec (split roots + bagging), including chunk-aligned inactive-peak handling.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
storage/src/qmdb/sync/database.rs Clarifies which root spec current uses when verifying sync ops proofs.
storage/src/qmdb/current/unordered/test_trait_impls.rs Adds RootSpec bound for unordered fixed-partitioned test DB trait impls.
storage/src/qmdb/current/unordered/mod.rs Updates tests for new RangeProof fields (suffix witnesses).
storage/src/qmdb/current/unordered/db.rs Propagates RootSpec bounds into unordered current DB impls.
storage/src/qmdb/current/sync/tests.rs Updates sync harness helpers to require RootSpec.
storage/src/qmdb/current/sync/mod.rs Threads RootSpec bounds + inactivity floor into sync rebuild/root computation.
storage/src/qmdb/current/proof.rs Major update: range proof now supports split roots + backward bagging with prefix/suffix witnesses and range-only proof collection.
storage/src/qmdb/current/ordered/test_trait_impls.rs Adds RootSpec bound for ordered test trait impls.
storage/src/qmdb/current/ordered/mod.rs Updates tests for new RangeProof fields (suffix witnesses).
storage/src/qmdb/current/mod.rs Updates initialization/tests to require RootSpec and to pass inactivity floor into root computation.
storage/src/qmdb/current/grafting.rs Implements grafted root computation using RootSpec + transformed inactive-peak mapping; adds chunk-aligned inactive-peak helper.
storage/src/qmdb/current/db.rs Propagates RootSpec bound and passes inactivity floor through grafted/canonical root computation + proof creation.
storage/src/qmdb/current/batch.rs Updates batch merkleization pipeline to compute canonical roots with inactivity floor and RootSpec bounds.
storage/src/merkle/proof.rs Adds APIs for collecting only range siblings (range-collection proof + verifier path) to avoid unused/malleable generic witnesses.
storage/src/merkle/mod.rs Re-exports new range-collection proof helpers under std.
storage/fuzz/fuzz_targets/current_unordered_operations.rs Extends fuzzing to tamper with new prefix/suffix peak witness vectors.
storage/fuzz/fuzz_targets/current_ordered_operations.rs Extends fuzzing to tamper with new prefix/suffix peak witness vectors.

Comment thread storage/src/merkle/proof.rs
Comment thread storage/src/qmdb/current/proof.rs Outdated
@roberto-bayardo roberto-bayardo force-pushed the pyramid-grafting branch 4 times, most recently from ab3f7a1 to bb50c8b Compare April 30, 2026 18:15
@roberto-bayardo roberto-bayardo added the breaking-format This PR modifies codec and/or storage formats. label Apr 30, 2026
@roberto-bayardo roberto-bayardo requested a review from Copilot April 30, 2026 18:15
@roberto-bayardo
Copy link
Copy Markdown
Collaborator Author

bugbot run

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for split (pyramid) bagging for qmdb::current databases by switching sync/proof/root handling to the QMDB per-family root spec (including non-zero inactive peak counts) and extending current proofs to carry the extra witness material needed for grafted-root reconstruction under backward bagging (MMB).

Changes:

  • Switch current ops trees + sync verification to QMDB per-family bagging (qmdb::Bagging) and propagate inactive peak counts end-to-end (sync target checks, proof verification, root computation).
  • Rework current::RangeProof generation/verification to support backward-bagged proofs (adds suffix witnesses; introduces a “range-collection-only” generic proof path to avoid malleable unused bytes).
  • Update grafted-root computation to use chunk-aligned inactive peak boundaries and update fuzz/conformance artifacts accordingly.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
storage/src/qmdb/sync/database.rs Clarifies current sync proofs are verified against the ops-tree spec (not canonical root).
storage/src/qmdb/current/unordered/test_trait_impls.rs Tightens test trait bounds to require Bagging for families.
storage/src/qmdb/current/unordered/mod.rs Updates RangeProof construction in tests for new fields (suffix witnesses).
storage/src/qmdb/current/unordered/db.rs Requires Bagging for current unordered DB variants.
storage/src/qmdb/current/sync/tests.rs Updates sync harness bounds to Graftable + Bagging.
storage/src/qmdb/current/sync/mod.rs Uses QMDB bagging + inactive peaks in sync DB/Resolver implementations and local-target checks.
storage/src/qmdb/current/proof.rs Major proof spec update: suffix witnesses, sparse proof path, inactivity-floor plumbing, and verifier changes.
storage/src/qmdb/current/ordered/test_trait_impls.rs Tightens test trait bounds to require Bagging for families.
storage/src/qmdb/current/ordered/mod.rs Updates RangeProof construction in tests for new fields (suffix witnesses).
storage/src/qmdb/current/mod.rs Forces split-root + QMDB bagging for underlying ops tree; updates docs and init wiring.
storage/src/qmdb/current/grafting.rs Adds chunk-aligned inactive peak logic and generalized grafted root computation under arbitrary bagging.
storage/src/qmdb/current/db.rs Propagates inactivity floor and QMDB bagging into grafted-root and proof-generation APIs.
storage/src/qmdb/current/batch.rs Propagates QMDB bagging/inactivity floor into batch merkleization + root computation paths.
storage/src/merkle/proof.rs Adds “range-collection-only” proof building + reconstruction helpers to support current grafted proofs safely.
storage/src/merkle/mod.rs Re-exports new range-collection proof helpers (std-only).
storage/fuzz/fuzz_targets/current_unordered_operations.rs Extends fuzzing to tamper with new prefix/suffix witness vectors.
storage/fuzz/fuzz_targets/current_ordered_operations.rs Extends fuzzing to tamper with new prefix/suffix witness vectors.
storage/conformance.toml Updates conformance hashes for the new root/proof behavior.

Comment thread storage/src/qmdb/current/proof.rs Outdated
Comment thread storage/src/qmdb/current/proof.rs
Comment thread storage/src/qmdb/current/proof.rs
Comment thread storage/src/qmdb/current/proof.rs
Comment thread storage/src/qmdb/current/proof.rs
@roberto-bayardo roberto-bayardo force-pushed the pyramid-mmb branch 3 times, most recently from fb14d2e to c975533 Compare May 1, 2026 17:03
Comment thread storage/src/qmdb/current/proof.rs
@roberto-bayardo roberto-bayardo force-pushed the pyramid-grafting branch 2 times, most recently from a82c6e4 to 2b7da92 Compare May 5, 2026 04:21
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2b7da92. Configure here.

Comment thread .github/benchmark-tracking.toml
@roberto-bayardo roberto-bayardo force-pushed the pyramid-grafting branch 2 times, most recently from 05c8caf to 96635d9 Compare May 5, 2026 15:20
Comment thread storage/src/qmdb/any/mod.rs Outdated
@roberto-bayardo roberto-bayardo requested a review from danlaine May 5, 2026 17:12
Comment thread storage/src/journal/authenticated.rs Outdated
UnmerkleizedBatch {
inner: self.inner.new_batch(),
hasher: StandardHasher::new(),
hasher: StandardHasher::new(ForwardFold),
Copy link
Copy Markdown
Collaborator

@danlaine danlaine May 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't broken since the UnmerkleizedBatch's use of the hasher isn't affected by the bagging policy, but still seems awkward since we may actually use this with BackwardFold

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't pick a default, what's the alternative though?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think of this?

diff --git a/storage/src/journal/authenticated.rs b/storage/src/journal/authenticated.rs
index a395eda2b..2a36d5ea9 100644
--- a/storage/src/journal/authenticated.rs
+++ b/storage/src/journal/authenticated.rs
@@ -15,8 +15,7 @@ use crate::{
         full::Merkle,
         hasher::{Hasher as _, Standard as StandardHasher},
         mem::Mem,
-        Bagging::ForwardFold,
-        Family, Location, Position, Proof, Readable,
+        Bagging, Family, Location, Position, Proof, Readable,
     },
     Context, Persistable,
 };
@@ -109,6 +108,7 @@ impl<F: Family, H: Hasher, Item: Encode + Send + Sync, S: Strategy>
         let ancestor_items = Self::collect_ancestor_items(&parent);
         Arc::new(MerkleizedBatch {
             inner: merkle,
+            bagging: hasher.bagging(),
             items,
             parent: parent.as_ref().map(Arc::downgrade),
             ancestor_items,
@@ -153,6 +153,7 @@ impl<F: Family, H: Hasher, Item: Encode + Send + Sync, S: Strategy>
         let ancestor_items = Self::collect_ancestor_items(&self.parent);
         Arc::new(MerkleizedBatch {
             inner: merkle,
+            bagging: self.hasher.bagging(),
             items,
             parent: self.parent.as_ref().map(Arc::downgrade),
             ancestor_items,
@@ -165,6 +166,8 @@ impl<F: Family, H: Hasher, Item: Encode + Send + Sync, S: Strategy>
 pub struct MerkleizedBatch<F: Family, D: Digest, Item: Send + Sync, S: Strategy = Sequential> {
     /// The inner batch of Merkle leaf digests.
     pub(crate) inner: Arc<batch::MerkleizedBatch<F, D, S>>,
+    /// The peak bagging policy inherited from the parent journal or batch.
+    bagging: Bagging,
     /// The items to append from this batch.
     items: Arc<Vec<Item>>,
     /// This batch's parent, or None if the parent is the journal itself.
@@ -227,7 +230,7 @@ impl<F: Family, D: Digest, Item: Send + Sync, S: Strategy> MerkleizedBatch<F, D,
     {
         UnmerkleizedBatch {
             inner: self.inner.new_batch(),
-            hasher: StandardHasher::new(ForwardFold),
+            hasher: StandardHasher::new(self.bagging),
             items: Vec::new(),
             parent: Some(Arc::clone(self)),
         }
@@ -319,7 +322,7 @@ where
         let root = self.merkle.to_batch();
         UnmerkleizedBatch {
             inner: root.new_batch(),
-            hasher: StandardHasher::new(ForwardFold),
+            hasher: StandardHasher::new(self.hasher.bagging()),
             items: Vec::new(),
             parent: None,
         }
@@ -337,6 +340,7 @@ where
     pub(crate) fn to_merkleized_batch(&self) -> Arc<MerkleizedBatch<F, H::Digest, C::Item, S>> {
         Arc::new(MerkleizedBatch {
             inner: self.merkle.to_batch(),
+            bagging: self.hasher.bagging(),
             items: Arc::new(Vec::new()),
             parent: None,
             ancestor_items: Vec::new(),
@@ -850,6 +854,7 @@ mod tests {
     use crate::{
         journal::contiguous::fixed::{Config as JConfig, Journal as ContiguousJournal},
         merkle::{
+            Bagging::ForwardFold,
             full::{Config as MerkleConfig, Merkle},
             mmb, mmr,
         },
diff --git a/storage/src/merkle/hasher.rs b/storage/src/merkle/hasher.rs
index e2463399e..4940056c9 100644
--- a/storage/src/merkle/hasher.rs
+++ b/storage/src/merkle/hasher.rs
@@ -162,6 +162,11 @@ impl<H: CHasher> Standard<H> {
         }
     }
 
+    /// Return the bagging policy used when folding peaks into a root.
+    pub const fn bagging(&self) -> Bagging {
+        self.bagging
+    }
+
     /// Hash an arbitrary sequence of byte slices into a single digest.
     pub fn hash<'a>(&self, parts: impl IntoIterator<Item = &'a [u8]>) -> H::Digest {
         let mut h = H::new();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread storage/src/journal/authenticated.rs Outdated
UnmerkleizedBatch {
inner: root.new_batch(),
hasher: StandardHasher::new(),
hasher: StandardHasher::new(ForwardFold),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread storage/src/qmdb/any/db.rs Outdated
|op| op.has_floor(),
)
.await?;
if inactivity_floor_loc > last_commit_loc {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already checked by find_inactivity_floor_at

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread storage/src/qmdb/current/mod.rs Outdated

/// Regression: MMB current-db ops proofs verify against the full-forward ops root.
/// Regression: `ops_historical_proof` must verify with QMDB's ops-tree hasher configuration,
/// not the Merkle default.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no default anymore

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread storage/src/merkle/hasher.rs Outdated
Comment on lines +171 to +176
Self::new(ForwardFold)
}

/// Creates a new [Standard] hasher with backward-fold bagging.
pub const fn backward() -> Self {
Self::with_bagging(Bagging::BackwardFold)
}

/// Creates a new [Standard] hasher with the given bagging policy.
pub const fn with_bagging(bagging: Bagging) -> Self {
Self {
_hasher: PhantomData,
bagging,
}
Self::new(BackwardFold)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need forward() and backward() now that there is no default? Or should user just use new?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed, removed.

@roberto-bayardo roberto-bayardo enabled auto-merge May 5, 2026 20:09
@codecov
Copy link
Copy Markdown

codecov Bot commented May 5, 2026

Codecov Report

❌ Patch coverage is 96.38129% with 41 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.83%. Comparing base (de6ecda) to head (3a36c86).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
storage/src/qmdb/current/grafting.rs 88.27% 14 Missing and 3 partials ⚠️
storage/src/qmdb/current/proof.rs 98.12% 6 Missing and 4 partials ⚠️
storage/src/merkle/proof.rs 91.74% 5 Missing and 4 partials ⚠️
storage/src/qmdb/any/sync/mod.rs 83.33% 2 Missing ⚠️
storage/src/journal/authenticated.rs 96.96% 1 Missing ⚠️
storage/src/qmdb/current/db.rs 97.05% 1 Missing ⚠️
storage/src/qmdb/current/sync/mod.rs 96.77% 1 Missing ⚠️
@@            Coverage Diff             @@
##             main    #3693      +/-   ##
==========================================
- Coverage   95.83%   95.83%   -0.01%     
==========================================
  Files         458      458              
  Lines      180036   180650     +614     
  Branches     4208     4218      +10     
==========================================
+ Hits       172531   173117     +586     
- Misses       6166     6187      +21     
- Partials     1339     1346       +7     
Files with missing lines Coverage Δ
storage/src/bitmap/authenticated.rs 92.43% <100.00%> (ø)
storage/src/merkle/batch.rs 95.80% <100.00%> (ø)
storage/src/merkle/conformance.rs 100.00% <100.00%> (ø)
storage/src/merkle/hasher.rs 97.20% <100.00%> (+1.22%) ⬆️
storage/src/merkle/mem.rs 97.65% <100.00%> (ø)
storage/src/merkle/mmb/mem.rs 100.00% <100.00%> (ø)
storage/src/merkle/mmb/mod.rs 96.95% <100.00%> (ø)
storage/src/merkle/mmb/proof.rs 96.96% <100.00%> (ø)
storage/src/merkle/mmr/full.rs 100.00% <100.00%> (ø)
storage/src/merkle/mmr/iterator.rs 100.00% <100.00%> (ø)
... and 39 more

... and 6 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update de6ecda...3a36c86. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@roberto-bayardo roberto-bayardo added this pull request to the merge queue May 5, 2026
@patrick-ogrady patrick-ogrady removed this pull request from the merge queue due to a manual request May 5, 2026
@patrick-ogrady patrick-ogrady merged commit dec68db into main May 5, 2026
182 checks passed
@patrick-ogrady patrick-ogrady deleted the pyramid-grafting branch May 5, 2026 21:08
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Tracker May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-format This PR modifies codec and/or storage formats.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants