Skip to content

fix: add version gating to proof generation methods - #513

Merged
QuantumExplorer merged 2 commits into
developfrom
fix/A2-add-version-gating-to-proof-methods
Mar 6, 2026
Merged

fix: add version gating to proof generation methods#513
QuantumExplorer merged 2 commits into
developfrom
fix/A2-add-version-gating-to-proof-methods

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Mar 5, 2026

Copy link
Copy Markdown
Member

Summary

  • Refactor prove_query and prove_query_non_serialized to use the version-dispatch pattern (matching Platform conventions)
  • prove_query delegates to prove_query_non_serialized which dispatches to v0 or v1 based on grove_version.grovedb_versions.operations.proof.prove_query_non_serialized
  • Versioned implementation methods (prove_query_non_serialized_v0, prove_query_non_serialized_v1) are pub(crate), not pub
  • Add check_grovedb_v0_or_v1_with_cost! macro for methods that accept both v0 and v1
  • Add grove version v3 with v1 proof generation enabled (prove_query_non_serialized: 1)
  • Update tests: v0-specific tests pinned to &GROVE_V2 with _for_version_2 suffix, original names use GroveVersion::latest()

Context

Audit finding A2: proof generation methods lacked version-based dispatch pattern used elsewhere in Platform.

Test plan

  • cargo build passes
  • cargo clippy -- -D warnings passes
  • cargo test -p grovedb --lib — 1265 passed, 0 failed

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added finer version flags and runtime gating for additional proof variants (including non-serialized and chunked trunk/branch proofs) to improve proof generation control and compatibility.
  • Public API

    • Unified V1-specific proof entry into a generic proof method and exposed a non-serialized proof entry for richer proof objects.
  • Chores

    • Added GROVE_V3 and enhanced version validation with clearer version-mismatch errors.
  • Tests

    • Updated and expanded tests to use explicit version constants (notably GROVE_V2) and to cover the new proof paths.

@coderabbitai

coderabbitai Bot commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added five new proof-version fields to GroveDBOperationsProofVersions and initialized them in v1/v2; introduced GROVE_V3 and its descriptor; added a version-with-cost macro; implemented version-dispatched non-serialized proof paths with cost guards; updated many tests and public proof API call sites.

Changes

Cohort / File(s) Summary
Version Types
grovedb-version/src/version/grovedb_versions.rs
Added public FeatureVersion fields to GroveDBOperationsProofVersions: prove_query_non_serialized, prove_trunk_chunk, prove_trunk_chunk_non_serialized, prove_branch_chunk, prove_branch_chunk_non_serialized.
Version Constants
grovedb-version/src/version/v1.rs, grovedb-version/src/version/v2.rs
Initialized the five new proof fields to 0 in GROVE_V1 and GROVE_V2.
New Version
grovedb-version/src/version/v3.rs, grovedb-version/src/version/mod.rs
Added GROVE_V3 descriptor (protocol_version=2, populated feature flags/costs) and included it in GROVE_VERSIONS.
Version Macros
grovedb-version/src/lib.rs
Added check_grovedb_v0_or_v1_with_cost! macro to accept versions 0 or 1 and return a cost-wrapped UnknownVersionMismatch on mismatch.
Proof generation (core)
grovedb/src/operations/proof/generate.rs
Added public prove_query_non_serialized with version-dispatched helpers prove_query_non_serialized_v0 and _v1; prove_query now routes through non-serialized path then serializes; introduced cost-aware gating and updated internal dispatch/comments.
Proof guards & dispatch
grovedb/src/operations/proof/generate.rs
Added cost-check macros (check_grovedb_v0_with_cost / check_grovedb_v0_or_v1_with_cost) around trunk/branch proof paths and routed calls through new non-serialized implementations.
Tests & API callsites
grovedb/src/tests/...
grovedb/src/tests/mod.rs, .../proof_coverage_tests.rs, .../commitment_tree_tests.rs, .../dense_tree_tests.rs, .../mmr_tree_tests.rs, .../v1_proof_tests.rs, .../provable_count_*
Updated many tests to use renamed public API (prove_query replaces prove_query_v1), adjusted non-serialized helper names, introduced/targeted GROVE_V2 in many tests, and added version-2-specific test variants.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant GroveDB as GroveDB
    participant VersionCheck as VersionChecker
    participant ProofGen as ProofGenerator
    participant CostSys as CostSystem

    Client->>GroveDB: prove_query(path_query, options, grove_version)
    GroveDB->>VersionCheck: check_grovedb_v0_or_v1_with_cost(grove_version)
    VersionCheck-->>GroveDB: ok / Err(UnknownVersionMismatch)
    alt version == 0
        GroveDB->>ProofGen: prove_query_non_serialized_v0(path_query, options)
        ProofGen->>CostSys: charge cost (v0 path)
        ProofGen-->>GroveDB: GroveDBProof (Merkle-only)
    else version == 1
        GroveDB->>ProofGen: prove_query_non_serialized_v1(path_query, options)
        ProofGen->>CostSys: charge cost (v1 path, mmr/bulk support)
        ProofGen-->>GroveDB: GroveDBProof (layered)
    end
    GroveDB->>GroveDB: serialize GroveDBProof -> Vec<u8>
    GroveDB-->>Client: Result<Vec<u8>, Error>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped through fields and versions new,
Five tiny flags in morning dew,
Paths split politely, costs kept in sight,
V3 arrived to join the flight,
Little paws applaud the proofing crew.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix: add version gating to proof generation methods' clearly summarizes the main change—adding version-based dispatch (gating) to proof generation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/A2-add-version-gating-to-proof-methods

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Mar 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 31.91489% with 32 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.69%. Comparing base (0274158) to head (8e9c91b).
⚠️ Report is 2 commits behind head on develop.

Files with missing lines Patch % Lines
grovedb/src/operations/proof/generate.rs 31.91% 32 Missing ⚠️

❌ Your patch status has failed because the patch coverage (31.91%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #513      +/-   ##
===========================================
- Coverage    90.69%   90.69%   -0.01%     
===========================================
  Files          182      182              
  Lines        49278    49311      +33     
===========================================
+ Hits         44694    44723      +29     
- Misses        4584     4588       +4     
Components Coverage Δ
grovedb-core 88.90% <31.91%> (-0.01%) ⬇️
merk 91.97% <ø> (ø)
storage 85.40% <ø> (ø)
commitment-tree 96.41% <ø> (ø)
mmr 96.72% <ø> (ø)
bulk-append-tree 91.50% <ø> (ø)
element 97.71% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Needs changes

pub struct GroveDBOperationsProofVersions {
pub prove_query: FeatureVersion,
pub prove_query_many: FeatureVersion,
pub prove_query_v1: FeatureVersion,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

there should eb no prove_query_v1, just prove_query

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Addressed in 7b3a2a6prove_query_v1 field removed; the method now correctly references the existing prove_query version field.

pub prove_trunk_chunk_non_serialized: FeatureVersion,
pub prove_branch_chunk: FeatureVersion,
pub prove_branch_chunk_non_serialized: FeatureVersion,
pub prove_query_v1_non_serialized: FeatureVersion,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

there should be no v1, as it's the v1 of prove_query_non_serialized

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also addressed in 7b3a2a6prove_query_v1_non_serialized field removed. The v1 methods correctly share version fields with their base methods now.

Comment on lines +107 to +110
check_grovedb_v0_with_cost!(
"prove_query_v1",
grove_version.grovedb_versions.operations.proof.prove_query
);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This should not not be check_grovedb_v0_with_cost, because it's v1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in 441a4cd — now uses check_grovedb_v0_or_v1_with_cost! which correctly accepts both v0 and v1 for the v1 proof methods. Also added the new check_grovedb_v0_or_v1_with_cost macro (cost-returning variant) to support this.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All three self-review comments have been addressed in the follow-up commits:

  1. 7b3a2a6: Removed prove_query_v1 and prove_query_v1_non_serialized version fields — v1 methods now correctly reuse prove_query and prove_query_non_serialized fields.
  2. 441a4cd: Changed prove_query_v1 and prove_query_v1_non_serialized checks from check_grovedb_v0_with_cost! to check_grovedb_v0_or_v1_with_cost! — correctly accepts both v0 and v1.

Quick review of the final state:

  • GroveDBOperationsProofVersions struct has 5 new fields (no v1 dupes)
  • GROVE_V1 and GROVE_V2 both initialize the new fields to 0
  • ✅ New check_grovedb_v0_or_v1_with_cost! macro mirrors the existing check_grovedb_v0_or_v1 but returns a cost-wrapped error
  • ✅ v0-only methods use check_grovedb_v0_with_cost!, v1 methods use check_grovedb_v0_or_v1_with_cost!

LGTM — all version gates look correct.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
grovedb/src/operations/proof/generate.rs (2)

119-124: ⚠️ Potential issue | 🟠 Major

Avoid shrinking the versioned proof API in this fix.

Making prove_query_v1 and prove_query_non_serialized_v1 pub(crate) turns this bugfix into a breaking library change for downstream crates that call the version-pinned methods directly. If the intent is to funnel callers through prove_query*, keep deprecated public wrappers for a release or document this as a semver-major change.

Also applies to: 842-847

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/operations/proof/generate.rs` around lines 119 - 124, The change
made the version-pinned APIs prove_query_v1 and prove_query_non_serialized_v1
non-public (pub(crate)), which is a breaking change for downstream crates;
restore a public surface by either reverting their visibility to pub or add back
thin public wrappers named prove_query_v1 and prove_query_non_serialized_v1 that
forward to the internal implementations (which can be renamed or kept
pub(crate)), and mark those wrappers #[deprecated] with a message pointing to
the new prove_query* entrypoints so callers are not broken immediately but
guided to migrate.

99-102: ⚠️ Potential issue | 🟠 Major

Call the V0 non-serialized helper directly.

prove_query_v0 currently re-dispatches through prove_query_non_serialized, so it depends on operations.proof.prove_query_non_serialized instead of the prove_query version that selected this branch. If those fields ever diverge, this can encode a V1 proof from the V0 path or reject an otherwise valid V0 call.

Suggested fix
         let proof = cost_return_on_error!(
             &mut cost,
-            self.prove_query_non_serialized(path_query, prove_options, grove_version)
+            self.prove_query_non_serialized_v0(path_query, prove_options, grove_version)
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/operations/proof/generate.rs` around lines 99 - 102, In
prove_query_v0, avoid re-dispatching through prove_query_non_serialized (which
ties V0 to the generic prove_query path); instead call the V0-specific
non-serialized helper directly (the V0 helper function used for non-serialized
proofs) so V0 uses its own implementation rather than
operations.proof.prove_query_non_serialized; update the call site in
prove_query_v0 to invoke that V0 non-serialized helper (keep prove_query and
prove_query_non_serialized unchanged) to ensure V0 proofs remain self-contained.
🧹 Nitpick comments (1)
grovedb/src/tests/proof_coverage_tests.rs (1)

1919-1953: This now skips the public version-gating path.

Calling prove_query_non_serialized_v1 directly only covers the internal helper rename. It no longer exercises the new prove_query_non_serialized dispatch/rejection logic that this PR adds. A companion test that drives the public method with an unsupported prove_query_non_serialized version would cover the actual regression surface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/tests/proof_coverage_tests.rs` around lines 1919 - 1953, The test
currently calls the internal helper prove_query_non_serialized_v1 directly and
therefore skips the public dispatch/rejection path; update the test (or add a
companion test) to call the public method prove_query_non_serialized with an
unsupported version value (e.g., a fake/latest+1 or an explicit unsupported enum
variant) and assert that it returns the expected rejection/error, while keeping
the existing setup (inserts, PathQuery, grove_version) so the dispatch logic is
exercised; reference prove_v1_non_serialized, prove_query_non_serialized, and
prove_query_non_serialized_v1 when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@grovedb/src/operations/proof/generate.rs`:
- Around line 119-124: The change made the version-pinned APIs prove_query_v1
and prove_query_non_serialized_v1 non-public (pub(crate)), which is a breaking
change for downstream crates; restore a public surface by either reverting their
visibility to pub or add back thin public wrappers named prove_query_v1 and
prove_query_non_serialized_v1 that forward to the internal implementations
(which can be renamed or kept pub(crate)), and mark those wrappers #[deprecated]
with a message pointing to the new prove_query* entrypoints so callers are not
broken immediately but guided to migrate.
- Around line 99-102: In prove_query_v0, avoid re-dispatching through
prove_query_non_serialized (which ties V0 to the generic prove_query path);
instead call the V0-specific non-serialized helper directly (the V0 helper
function used for non-serialized proofs) so V0 uses its own implementation
rather than operations.proof.prove_query_non_serialized; update the call site in
prove_query_v0 to invoke that V0 non-serialized helper (keep prove_query and
prove_query_non_serialized unchanged) to ensure V0 proofs remain self-contained.

---

Nitpick comments:
In `@grovedb/src/tests/proof_coverage_tests.rs`:
- Around line 1919-1953: The test currently calls the internal helper
prove_query_non_serialized_v1 directly and therefore skips the public
dispatch/rejection path; update the test (or add a companion test) to call the
public method prove_query_non_serialized with an unsupported version value
(e.g., a fake/latest+1 or an explicit unsupported enum variant) and assert that
it returns the expected rejection/error, while keeping the existing setup
(inserts, PathQuery, grove_version) so the dispatch logic is exercised;
reference prove_v1_non_serialized, prove_query_non_serialized, and
prove_query_non_serialized_v1 when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a1ab11f9-d288-45e7-a309-e261e629632d

📥 Commits

Reviewing files that changed from the base of the PR and between 441a4cd and 1e6bdb5.

📒 Files selected for processing (2)
  • grovedb/src/operations/proof/generate.rs
  • grovedb/src/tests/proof_coverage_tests.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
grovedb/src/tests/provable_count_tree_comprehensive_test.rs (1)

122-123: ⚠️ Potential issue | 🟠 Major

Add insert_all() to query to match full-tree semantics across this file.

Both lines 122-123 and 752-753 build an empty Query without calling insert_all(), despite the test intent and comment ("all items"). All other full-tree queries in this file (lines 235-236, 354-355, 481-482, 607-608) explicitly call insert_all() after Query::new(). Without it, these tests query an empty set instead of validating proof generation for counted-tree content.

Suggested change
-        let query = Query::new(); // Empty query gets all items
+        let mut query = Query::new();
+        query.insert_all();
         let path_query = PathQuery::new_unsized(vec![b"counts".to_vec()], query);
-        let query = Query::new();
+        let mut query = Query::new();
+        query.insert_all();
         let path_query = PathQuery::new_unsized(vec![b"counts".to_vec()], query);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/tests/provable_count_tree_comprehensive_test.rs` around lines 122
- 123, The test builds empty queries with Query::new() then wraps them with
PathQuery::new_unsized(...) but never calls insert_all(), so the queries return
nothing; update both occurrences that construct the query used with
PathQuery::new_unsized to call insert_all() on the Query (i.e., replace
Query::new() with Query::new().insert_all() or call .insert_all() before
creating the PathQuery) so the queries match full-tree semantics for
counted-tree proofs.
♻️ Duplicate comments (1)
grovedb/src/operations/proof/generate.rs (1)

77-80: ⚠️ Potential issue | 🔴 Critical

Use the v0/v1 guard here, or V1 proofs never reach the dispatcher.

prove_query_non_serialized() now handles both proof versions, but this v0-only gate rejects operations.proof.prove_query == 1 first. That makes the public prove_query() API unusable for the new V1/GROVE_V3 path and breaks the renamed test call sites that now go through it.

Suggested fix
-use grovedb_version::{check_grovedb_v0_with_cost, version::GroveVersion};
+use grovedb_version::{
+    check_grovedb_v0_or_v1_with_cost, check_grovedb_v0_with_cost, version::GroveVersion,
+};
...
-        check_grovedb_v0_with_cost!(
+        check_grovedb_v0_or_v1_with_cost!(
             "prove_query",
             grove_version.grovedb_versions.operations.proof.prove_query
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/operations/proof/generate.rs` around lines 77 - 80, The v0-only
check_grovedb_v0_with_cost! gate around prove_query blocks the V1 path so public
prove_query() never reaches the dispatcher; change the guard to allow both v0
and v1 (or use the v0/v1-aware check macro) so prove_query_non_serialized() can
handle both versions. Locate the call using check_grovedb_v0_with_cost! around
"prove_query" and replace it with the v0/v1 guard variant (or remove the strict
v0 macro) so operations.proof.prove_query == 1 is accepted and dispatches into
prove_query_non_serialized().
🧹 Nitpick comments (1)
grovedb/src/tests/mod.rs (1)

1875-2059: Extract the shared proof fixture/assertion flow into helpers.

These four tests rebuild nearly the same trees and repeat the same prove/verify/assert logic. A small helper parameterized by &GroveVersion plus an optional expected hex string would make future proof-version updates much easier to keep in sync.

Also applies to: 2062-2234, 2236-2434, 2436-2624

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/tests/mod.rs` around lines 1875 - 2059, The test
test_path_query_proofs_without_subquery_with_reference_for_version_2 (and the
other tests listed) duplicate tree setup and prove/verify/assert flow; extract a
small helper (e.g. build_test_tree_and_run_proof or assert_proof_for_version)
that accepts &GroveVersion and an optional expected hex string and performs:
create DB via make_test_grovedb, insert the same nodes (use the same keys:
TEST_LEAF, ANOTHER_TEST_LEAF, innertree/innertree2/innertree3, key1..key5 and
reference inserts), run temp_db.prove_query(&path_query, ...), call
GroveDb::verify_query_raw, compare root hash to temp_db.root_hash, and call
compare_result_tuples with expected serialized Elements; then replace the body
of test_path_query_proofs_without_subquery_with_reference_for_version_2 (and the
other repeated tests) with calls to that helper passing the grove version and
expected hex string where needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@grovedb-version/src/version/mod.rs`:
- Line 35: Tests in grovedb-version need updating because GROVE_V3 was added to
the GROVE_VERSIONS array, which invalidates assertions in
grovedb-version/src/tests.rs; update the test expectations to reflect the new
version list and behavior: change any length assertion (currently asserting
GROVE_VERSIONS.len() == 2) to the new count including GROVE_V3, and update
expectations for GroveVersion::latest() to return GROVE_V3 (or otherwise compute
expected latest from GROVE_VERSIONS) so tests reference the GROVE_VERSIONS
constant and GroveVersion::latest() symbol instead of hard-coded old values.

In `@grovedb/src/tests/mod.rs`:
- Line 52: The tests currently reference GroveVersion::latest() for the proof-v1
dispatch paths; change them to use the concrete constant for the intended fixed
version (import and use GROVE_V3) so the tests are deterministic. Update the
imports at the top to include grovedb_version::version::v3::GROVE_V3 (in
addition to the existing GROVE_V2 import) and replace occurrences of
GroveVersion::latest() in the proof-v1 dispatch tests (the test blocks around
the "proof-v1" dispatch checks) with GROVE_V3.

In `@grovedb/src/tests/proof_coverage_tests.rs`:
- Line 17: Tests that assert V1 dispatch currently call GroveVersion::latest(),
which will break when latest() advances; change those V1-specific tests to use
the explicit V1 version constant instead. Import and use the concrete V1
constant (e.g. grovedb_version::version::v1::GROVE_V1) in place of
GroveVersion::latest() in the tests that validate operations.proof.prove_query
mapping to 1 (references: the test file's use of GroveVersion::latest() and the
proof coverage assertions that expect prove_query => 1).
- Around line 1950-1953: The test is calling prove_query_non_serialized_v1(...)
directly which bypasses the dispatcher; change it to call
prove_query_non_serialized(&path_query, None, grove_version) on db (the
dispatcher) and then assert the result is a GroveDBProof::V1 variant (e.g. match
or if let to confirm GroveDBProof::V1(_)) before unwrapping/using the inner V1
proof so the test verifies correct routing through the dispatcher.

In `@grovedb/src/tests/provable_count_sum_tree_tests.rs`:
- Around line 2656-2673: The current check_v1_layer_proof returns early when
layer.merk_proof is not ProofBytes::Merk, which prevents descending into
layer.lower_layers and misses counted child Merk layers under non-Merk parents;
update the function so that non-Merk cases do not return but instead skip
decoding and still iterate/recursively call check_v1_layer_proof on
layer.lower_layers (e.g., replace the '_ => return' behavior with a no-op or use
if let ProofBytes::Merk(...) { ... } and always recurse into lower_layers),
preserving the found_kvdigest_count update logic for Merk bytes.

---

Outside diff comments:
In `@grovedb/src/tests/provable_count_tree_comprehensive_test.rs`:
- Around line 122-123: The test builds empty queries with Query::new() then
wraps them with PathQuery::new_unsized(...) but never calls insert_all(), so the
queries return nothing; update both occurrences that construct the query used
with PathQuery::new_unsized to call insert_all() on the Query (i.e., replace
Query::new() with Query::new().insert_all() or call .insert_all() before
creating the PathQuery) so the queries match full-tree semantics for
counted-tree proofs.

---

Duplicate comments:
In `@grovedb/src/operations/proof/generate.rs`:
- Around line 77-80: The v0-only check_grovedb_v0_with_cost! gate around
prove_query blocks the V1 path so public prove_query() never reaches the
dispatcher; change the guard to allow both v0 and v1 (or use the v0/v1-aware
check macro) so prove_query_non_serialized() can handle both versions. Locate
the call using check_grovedb_v0_with_cost! around "prove_query" and replace it
with the v0/v1 guard variant (or remove the strict v0 macro) so
operations.proof.prove_query == 1 is accepted and dispatches into
prove_query_non_serialized().

---

Nitpick comments:
In `@grovedb/src/tests/mod.rs`:
- Around line 1875-2059: The test
test_path_query_proofs_without_subquery_with_reference_for_version_2 (and the
other tests listed) duplicate tree setup and prove/verify/assert flow; extract a
small helper (e.g. build_test_tree_and_run_proof or assert_proof_for_version)
that accepts &GroveVersion and an optional expected hex string and performs:
create DB via make_test_grovedb, insert the same nodes (use the same keys:
TEST_LEAF, ANOTHER_TEST_LEAF, innertree/innertree2/innertree3, key1..key5 and
reference inserts), run temp_db.prove_query(&path_query, ...), call
GroveDb::verify_query_raw, compare root hash to temp_db.root_hash, and call
compare_result_tuples with expected serialized Elements; then replace the body
of test_path_query_proofs_without_subquery_with_reference_for_version_2 (and the
other repeated tests) with calls to that helper passing the grove version and
expected hex string where needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b1f9ca99-b8ed-469a-8756-71c0fd2e8327

📥 Commits

Reviewing files that changed from the base of the PR and between 1e6bdb5 and 2c1fc47.

📒 Files selected for processing (11)
  • grovedb-version/src/version/mod.rs
  • grovedb-version/src/version/v3.rs
  • grovedb/src/operations/proof/generate.rs
  • grovedb/src/tests/commitment_tree_tests.rs
  • grovedb/src/tests/dense_tree_tests.rs
  • grovedb/src/tests/mmr_tree_tests.rs
  • grovedb/src/tests/mod.rs
  • grovedb/src/tests/proof_coverage_tests.rs
  • grovedb/src/tests/provable_count_sum_tree_tests.rs
  • grovedb/src/tests/provable_count_tree_comprehensive_test.rs
  • grovedb/src/tests/v1_proof_tests.rs

Comment thread grovedb-version/src/version/mod.rs
Comment thread grovedb/src/tests/mod.rs
};

use grovedb_version::version::GroveVersion;
use grovedb_version::version::{v2::GROVE_V2, GroveVersion};

@coderabbitai coderabbitai Bot Mar 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Pin the proof-v1 tests to a concrete version instead of latest().

Line 2063 and Line 2438 make these new dispatch tests follow whatever GroveVersion::latest() becomes next, so they stop being a deterministic check of the proof-v1 path introduced in this PR. Please import and use the intended fixed version constant here (likely GROVE_V3), the same way the legacy path is pinned to GROVE_V2.

🩹 Suggested change
-use grovedb_version::version::{v2::GROVE_V2, GroveVersion};
+use grovedb_version::version::{v2::GROVE_V2, v3::GROVE_V3, GroveVersion};
...
 fn test_path_query_proofs_without_subquery_with_reference() {
-    let grove_version = GroveVersion::latest();
+    let grove_version = &GROVE_V3;
...
 fn test_path_query_proofs_without_subquery() {
-    let grove_version = GroveVersion::latest();
+    let grove_version = &GROVE_V3;

Also applies to: 2062-2234, 2436-2624

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/tests/mod.rs` at line 52, The tests currently reference
GroveVersion::latest() for the proof-v1 dispatch paths; change them to use the
concrete constant for the intended fixed version (import and use GROVE_V3) so
the tests are deterministic. Update the imports at the top to include
grovedb_version::version::v3::GROVE_V3 (in addition to the existing GROVE_V2
import) and replace occurrences of GroveVersion::latest() in the proof-v1
dispatch tests (the test blocks around the "proof-v1" dispatch checks) with
GROVE_V3.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

no need

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@QuantumExplorer, understood — I'll leave the tests using GroveVersion::latest() as-is.


🧠 Learnings used
Learnt from: QuantumExplorer
Repo: dashpay/grovedb PR: 397
File: grovedb/src/tests/provable_count_sum_tree_tests.rs:82-90
Timestamp: 2025-12-17T18:39:16.131Z
Learning: In Rust test code for grovedb, when a function returns a cost-wrapped result (CostResult), using .unwrap().map_err(|e| e) is acceptable to discard cost tracking by unwrapping the inner Result and converting the error type for test helpers. This pattern should be applied across test files that interact with cost-wrapped results, not just a single test, to ensure consistency in how test helpers handle costs.

Query,
};
use grovedb_version::version::GroveVersion;
use grovedb_version::version::{v2::GROVE_V2, GroveVersion};

@coderabbitai coderabbitai Bot Mar 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Pin the V1 coverage to a concrete Grove version.

These V1-specific tests still run through GroveVersion::latest(), so the moment latest() moves again they stop validating the V1 dispatch this PR is adding. Please use the explicit version constant that maps operations.proof.prove_query to 1 instead of relying on latest().

Suggested pattern
-use grovedb_version::version::{v2::GROVE_V2, GroveVersion};
+use grovedb_version::version::{v2::GROVE_V2, v3::GROVE_V3, GroveVersion};

-        let grove_version = GroveVersion::latest();
+        let grove_version = &GROVE_V3;

Also applies to: 647-647, 1104-1104, 1732-1732, 2437-2437, 3323-3323, 4025-4025, 5002-5002, 5197-5197, 5376-5376

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/tests/proof_coverage_tests.rs` at line 17, Tests that assert V1
dispatch currently call GroveVersion::latest(), which will break when latest()
advances; change those V1-specific tests to use the explicit V1 version constant
instead. Import and use the concrete V1 constant (e.g.
grovedb_version::version::v1::GROVE_V1) in place of GroveVersion::latest() in
the tests that validate operations.proof.prove_query mapping to 1 (references:
the test file's use of GroveVersion::latest() and the proof coverage assertions
that expect prove_query => 1).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

no need

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Understood, acknowledged! 🐇


🧠 Learnings used
Learnt from: QuantumExplorer
Repo: dashpay/grovedb PR: 397
File: grovedb/src/tests/provable_count_sum_tree_tests.rs:82-90
Timestamp: 2025-12-17T18:39:16.131Z
Learning: In Rust test code for grovedb, when a function returns a cost-wrapped result (CostResult), using .unwrap().map_err(|e| e) is acceptable to discard cost tracking by unwrapping the inner Result and converting the error type for test helpers. This pattern should be applied across test files that interact with cost-wrapped results, not just a single test, to ensure consistency in how test helpers handle costs.

Comment thread grovedb/src/tests/proof_coverage_tests.rs
Comment on lines +2656 to +2673
fn check_v1_layer_proof(
layer: &crate::operations::proof::LayerProof,
found_kvdigest_count: &mut bool,
) {
let merk_bytes = match &layer.merk_proof {
ProofBytes::Merk(bytes) => bytes.as_slice(),
_ => return,
};
let decoder = Decoder::new(merk_bytes);
let ops: Vec<Op> = decoder.collect::<Result<Vec<_>, _>>().unwrap_or_default();

if has_kvdigest_count(&ops) {
*found_kvdigest_count = true;
}

for lower_layer in layer.lower_layers.values() {
check_v1_layer_proof(lower_layer, found_kvdigest_count);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't stop descending when a V1 proof layer isn't ProofBytes::Merk.

V1 proofs can have non-Merk parent layers. Returning here skips lower_layers entirely, so this search misses counted child Merk layers under MMR/BulkAppend ancestors and can false-fail on a valid proof.

Suggested change
                 fn check_v1_layer_proof(
                     layer: &crate::operations::proof::LayerProof,
                     found_kvdigest_count: &mut bool,
                 ) {
-                    let merk_bytes = match &layer.merk_proof {
-                        ProofBytes::Merk(bytes) => bytes.as_slice(),
-                        _ => return,
-                    };
-                    let decoder = Decoder::new(merk_bytes);
-                    let ops: Vec<Op> = decoder.collect::<Result<Vec<_>, _>>().unwrap_or_default();
-
-                    if has_kvdigest_count(&ops) {
-                        *found_kvdigest_count = true;
-                    }
+                    if let ProofBytes::Merk(bytes) = &layer.merk_proof {
+                        let decoder = Decoder::new(bytes.as_slice());
+                        let ops: Vec<Op> =
+                            decoder.collect::<Result<Vec<_>, _>>().unwrap_or_default();
+
+                        if has_kvdigest_count(&ops) {
+                            *found_kvdigest_count = true;
+                        }
+                    }
 
                     for lower_layer in layer.lower_layers.values() {
                         check_v1_layer_proof(lower_layer, found_kvdigest_count);
                     }
                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn check_v1_layer_proof(
layer: &crate::operations::proof::LayerProof,
found_kvdigest_count: &mut bool,
) {
let merk_bytes = match &layer.merk_proof {
ProofBytes::Merk(bytes) => bytes.as_slice(),
_ => return,
};
let decoder = Decoder::new(merk_bytes);
let ops: Vec<Op> = decoder.collect::<Result<Vec<_>, _>>().unwrap_or_default();
if has_kvdigest_count(&ops) {
*found_kvdigest_count = true;
}
for lower_layer in layer.lower_layers.values() {
check_v1_layer_proof(lower_layer, found_kvdigest_count);
}
fn check_v1_layer_proof(
layer: &crate::operations::proof::LayerProof,
found_kvdigest_count: &mut bool,
) {
if let ProofBytes::Merk(bytes) = &layer.merk_proof {
let decoder = Decoder::new(bytes.as_slice());
let ops: Vec<Op> =
decoder.collect::<Result<Vec<_>, _>>().unwrap_or_default();
if has_kvdigest_count(&ops) {
*found_kvdigest_count = true;
}
}
for lower_layer in layer.lower_layers.values() {
check_v1_layer_proof(lower_layer, found_kvdigest_count);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/tests/provable_count_sum_tree_tests.rs` around lines 2656 - 2673,
The current check_v1_layer_proof returns early when layer.merk_proof is not
ProofBytes::Merk, which prevents descending into layer.lower_layers and misses
counted child Merk layers under non-Merk parents; update the function so that
non-Merk cases do not return but instead skip decoding and still
iterate/recursively call check_v1_layer_proof on layer.lower_layers (e.g.,
replace the '_ => return' behavior with a no-op or use if let
ProofBytes::Merk(...) { ... } and always recurse into lower_layers), preserving
the found_kvdigest_count update logic for Merk bytes.

- prove_query delegates to prove_query_non_serialized which dispatches
  to v0 or v1 based on grove_version
- Versioned methods (prove_query_non_serialized_v0/v1) are pub(crate)
- Add check_grovedb_v0_or_v1_with_cost! macro
- Add grove version v3 with v1 proof generation enabled
- Pin v0-specific tests to &GROVE_V2 with _for_version_2 suffix
- Original test names use GroveVersion::latest()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer force-pushed the fix/A2-add-version-gating-to-proof-methods branch from 2c1fc47 to faea2ca Compare March 6, 2026 03:06
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
grovedb/src/operations/proof/generate.rs (4)

1418-1428: ⚠️ Potential issue | 🟠 Major

Don't hide commitment-frontier read or decode failures.

Falling back to EMPTY_SINSEMILLA_ROOT here turns storage corruption or I/O failures into a valid-looking proof bound to the wrong anchor. Only Ok(None) should use the empty root; read and deserialize errors should fail with CorruptedData and context.

As per coding guidelines, "Wrap errors with context using .map_err(|e| Error::CorruptedData(format!("context: {}", e))) pattern in Rust source files".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/operations/proof/generate.rs` around lines 1418 - 1428, The
current match silently maps read or deserialize failures to
grovedb_commitment_tree::EMPTY_SINSEMILLA_ROOT; change this so only Ok(None)
returns EMPTY_SINSEMILLA_ROOT and any storage read error from
storage_ctx.get(COMMITMENT_TREE_DATA_KEY).value or deserialize error from
grovedb_commitment_tree::CommitmentFrontier::deserialize(frontier_bytes.as_ref())
is propagated as Error::CorruptedData with context (use .map_err(|e|
Error::CorruptedData(format!("...: {}", e))) or equivalent), and adjust the
surrounding function to return a Result so failures bubble up instead of
producing a wrong proof root. Ensure references to COMMITMENT_TREE_DATA_KEY,
CommitmentFrontier::deserialize, EMPTY_SINSEMILLA_ROOT, and Error::CorruptedData
are used as described.

1330-1375: ⚠️ Potential issue | 🟠 Major

Don't decrement overall_limit by the full BulkAppend span.

Lines 1331-1334 collapse arbitrary query items into a single [start, end) interval, and Line 1373 subtracts that whole span from overall_limit. For sparse queries, this can consume far more slots than the proof actually returns; once the span exceeds u16::MAX, the as u16 cast truncates before saturating_sub. Please base the decrement on the actual proved positions/results and clamp before converting to u16.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/operations/proof/generate.rs` around lines 1330 - 1375, The code
currently computes a span (start, end) via query_items_to_range and subtracts
(end - start) cast to u16 from overall_limit, which overestimates for sparse
queries and truncates when the span exceeds u16::MAX; instead, compute the
actual number of returned/proved entries from the generated proof (use the
result from BulkAppendTreeProof::generate, e.g. count the proved
positions/values in bulk_proof), clamp that count to u16::MAX before casting,
and then perform the saturating_sub on overall_limit using that clamped
actual_count; update the code around the overall_limit handling (where start/end
and the current count are used) to read the real proved count rather than the
full [start,end) span.

1635-1662: ⚠️ Potential issue | 🟠 Major

Guard the inclusive / RangeAfter endpoint math against overflow.

These branches do start + 1 / end + 1 on decoded u16/u64 bounds. A query containing 0xFFFF or u64::MAX will overflow here, panicking in debug builds and wrapping in release. Please switch to checked_add(1) and reject overflow with Error::InvalidInput.

Also applies to: 1759-1782, 1803-1854

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/operations/proof/generate.rs` around lines 1635 - 1662, The code
does unchecked start+1 / end+1 on decoded u16/u64 values (in
QueryItem::RangeAfter, RangeAfterTo, RangeAfterToInclusive and similar blocks),
which can overflow; update each occurrence where you do start + 1 or adjust end
to use checked_add(1) (e.g. let start =
Self::decode_be_u16(...)?.checked_add(1).ok_or(Error::InvalidInput("query range
overflow"))? ) and similarly for end adjustments, and if checked_add returns
None return Error::InvalidInput with a clear message; apply the same change to
the other similar branches mentioned (the blocks around the other line ranges)
and keep using MAX_INDICES and Error::InvalidInput for the overflow rejection.

993-1013: ⚠️ Potential issue | 🟠 Major

Empty MMR subqueries are treated as hits.

generate_mmr_layer_proof returns an empty proof when mmr_size == 0, but Line 1012 still marks the level as having a result. That bypasses the empty-subquery decrement at Lines 1187-1192, so limit behavior now depends on whether the subtree is Merk or MMR. Please mirror the Merk path here and only mark a hit when the lower proof actually consumes from overall_limit.

Also applies to: 1187-1192, 1235-1254

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/operations/proof/generate.rs` around lines 993 - 1013,
generate_mmr_layer_proof currently returns an empty proof for mmr_size == 0, but
the code unconditionally sets has_a_result_at_level and inserts into
lower_layers; change this to mirror the Merk path by inspecting the returned
layer_proof and only treating it as a hit when it actually consumed from
overall_limit (e.g., proof is not empty or proof.consumed_count > 0 /
!proof.is_empty()), so set has_a_result_at_level |= true and
lower_layers.insert(key.clone(), layer_proof) only when that condition is true;
apply the same conditional check to the other MMR handling sites referenced (the
other occurrences analogous to the Merk path at the noted locations).
♻️ Duplicate comments (2)
grovedb/src/tests/provable_count_sum_tree_tests.rs (1)

2656-2673: ⚠️ Potential issue | 🟠 Major

Keep descending through V1 non-Merk layers.

_ => return skips lower_layers, so this helper can miss counted child Merk layers under non-Merk parents and fail on a valid V1 proof.

🩹 Suggested fix
                 fn check_v1_layer_proof(
                     layer: &crate::operations::proof::LayerProof,
                     found_kvdigest_count: &mut bool,
                 ) {
-                    let merk_bytes = match &layer.merk_proof {
-                        ProofBytes::Merk(bytes) => bytes.as_slice(),
-                        _ => return,
-                    };
-                    let decoder = Decoder::new(merk_bytes);
-                    let ops: Vec<Op> = decoder.collect::<Result<Vec<_>, _>>().unwrap_or_default();
-
-                    if has_kvdigest_count(&ops) {
-                        *found_kvdigest_count = true;
+                    if let ProofBytes::Merk(bytes) = &layer.merk_proof {
+                        let decoder = Decoder::new(bytes.as_slice());
+                        let ops: Vec<Op> =
+                            decoder.collect::<Result<Vec<_>, _>>().unwrap_or_default();
+
+                        if has_kvdigest_count(&ops) {
+                            *found_kvdigest_count = true;
+                        }
                     }
 
                     for lower_layer in layer.lower_layers.values() {
                         check_v1_layer_proof(lower_layer, found_kvdigest_count);
                     }

Verify by confirming that LayerProof always has lower_layers while ProofBytes has non-Merk variants, so recursion must not depend on the byte kind:

#!/bin/bash
set -euo pipefail

rg -n -C3 'fn check_v1_layer_proof|ProofBytes::Merk|lower_layers' grovedb/src/tests/provable_count_sum_tree_tests.rs
rg -n -C2 'struct LayerProof|enum ProofBytes|ProofBytes::' --type rust
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/tests/provable_count_sum_tree_tests.rs` around lines 2656 - 2673,
The helper check_v1_layer_proof currently returns early on non-Merk ProofBytes
(match arm `_ => return`), which skips recursing into layer.lower_layers and can
miss child Merk layers; change it so recursion always descends into
layer.lower_layers regardless of the ProofBytes variant and only attempt to
decode/collect ops when ProofBytes::Merk is present (i.e., match to get
merk_bytes and run Decoder there, but do not return from the function on other
variants), keeping the recursion over lower_layers after/independent of the
match; update check_v1_layer_proof (and any borrow usage) accordingly so
lower_layers are always traversed.
grovedb/src/tests/mod.rs (1)

52-52: ⚠️ Potential issue | 🟠 Major

Pin the proof-V1 dispatch tests to GROVE_V3 instead of GroveVersion::latest().

These are the V1-path counterparts to the new _for_version_2 tests. Using latest() makes them stop being a deterministic guard for the V1 dispatch path as soon as latest() moves forward.

🩹 Suggested fix
-use grovedb_version::version::{v2::GROVE_V2, GroveVersion};
+use grovedb_version::version::{v2::GROVE_V2, v3::GROVE_V3, GroveVersion};
...
     #[test]
     fn test_path_query_proofs_without_subquery_with_reference() {
-        let grove_version = GroveVersion::latest();
+        let grove_version = &GROVE_V3;
...
     #[test]
     fn test_path_query_proofs_without_subquery() {
-        let grove_version = GroveVersion::latest();
+        let grove_version = &GROVE_V3;

Verify by checking that latest() currently resolves to the concrete V1-proof version and that these tests still call GroveVersion::latest() directly:

#!/bin/bash
set -euo pipefail

rg -n -C2 'fn test_path_query_proofs_without_subquery_with_reference\b|fn test_path_query_proofs_without_subquery\b|GroveVersion::latest\(\)' grovedb/src/tests/mod.rs
rg -n -C2 'fn latest\(|GROVE_V3|prove_query_non_serialized' --type rust

Also applies to: 2062-2234, 2436-2624

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/tests/mod.rs` at line 52, The tests for the V1 dispatch path
should be pinned to the concrete GROVE_V3 constant rather than calling
GroveVersion::latest(); update the import and usages accordingly: change the use
line to import GROVE_V3 (replace any GROVE_V2 import) and replace calls to
GroveVersion::latest() in the test functions (e.g.,
test_path_query_proofs_without_subquery_with_reference,
test_path_query_proofs_without_subquery and related prove_query_non_serialized
tests) with the GROVE_V3 symbol so the tests deterministically target the
V1-proof dispatch path; also scan the nearby test ranges noted (around the given
function blocks) and replace any other latest() occurrences with GROVE_V3.
🧹 Nitpick comments (1)
grovedb/src/tests/provable_count_tree_comprehensive_test.rs (1)

719-834: Pin this proof-shape test to an explicit Grove version.

This test uses GroveVersion::latest() but then hard-codes GroveDBProof::V1. Since latest() is just the tail of GROVE_VERSIONS, the next proof-format bump will break this test for the wrong reason. Use the explicit version constant that is supposed to emit V1 proofs here instead of latest().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/tests/provable_count_tree_comprehensive_test.rs` around lines 719
- 834, The test test_provable_count_tree_proof_contains_count_nodes currently
uses GroveVersion::latest() but asserts on GroveDBProof::V1; replace the dynamic
latest() with the explicit V1 grove version constant (e.g., GroveVersion::V1 or
the project’s named V1 constant) where grove_version is set so the test is
pinned to the proof format that produces GroveDBProof::V1; update the
instantiation at the top of the test (the grove_version binding used in db
creation, db.insert calls, and prove_query) to use that explicit V1 constant
instead of GroveVersion::latest().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@grovedb/src/operations/proof/generate.rs`:
- Around line 1418-1428: The current match silently maps read or deserialize
failures to grovedb_commitment_tree::EMPTY_SINSEMILLA_ROOT; change this so only
Ok(None) returns EMPTY_SINSEMILLA_ROOT and any storage read error from
storage_ctx.get(COMMITMENT_TREE_DATA_KEY).value or deserialize error from
grovedb_commitment_tree::CommitmentFrontier::deserialize(frontier_bytes.as_ref())
is propagated as Error::CorruptedData with context (use .map_err(|e|
Error::CorruptedData(format!("...: {}", e))) or equivalent), and adjust the
surrounding function to return a Result so failures bubble up instead of
producing a wrong proof root. Ensure references to COMMITMENT_TREE_DATA_KEY,
CommitmentFrontier::deserialize, EMPTY_SINSEMILLA_ROOT, and Error::CorruptedData
are used as described.
- Around line 1330-1375: The code currently computes a span (start, end) via
query_items_to_range and subtracts (end - start) cast to u16 from overall_limit,
which overestimates for sparse queries and truncates when the span exceeds
u16::MAX; instead, compute the actual number of returned/proved entries from the
generated proof (use the result from BulkAppendTreeProof::generate, e.g. count
the proved positions/values in bulk_proof), clamp that count to u16::MAX before
casting, and then perform the saturating_sub on overall_limit using that clamped
actual_count; update the code around the overall_limit handling (where start/end
and the current count are used) to read the real proved count rather than the
full [start,end) span.
- Around line 1635-1662: The code does unchecked start+1 / end+1 on decoded
u16/u64 values (in QueryItem::RangeAfter, RangeAfterTo, RangeAfterToInclusive
and similar blocks), which can overflow; update each occurrence where you do
start + 1 or adjust end to use checked_add(1) (e.g. let start =
Self::decode_be_u16(...)?.checked_add(1).ok_or(Error::InvalidInput("query range
overflow"))? ) and similarly for end adjustments, and if checked_add returns
None return Error::InvalidInput with a clear message; apply the same change to
the other similar branches mentioned (the blocks around the other line ranges)
and keep using MAX_INDICES and Error::InvalidInput for the overflow rejection.
- Around line 993-1013: generate_mmr_layer_proof currently returns an empty
proof for mmr_size == 0, but the code unconditionally sets has_a_result_at_level
and inserts into lower_layers; change this to mirror the Merk path by inspecting
the returned layer_proof and only treating it as a hit when it actually consumed
from overall_limit (e.g., proof is not empty or proof.consumed_count > 0 /
!proof.is_empty()), so set has_a_result_at_level |= true and
lower_layers.insert(key.clone(), layer_proof) only when that condition is true;
apply the same conditional check to the other MMR handling sites referenced (the
other occurrences analogous to the Merk path at the noted locations).

---

Duplicate comments:
In `@grovedb/src/tests/mod.rs`:
- Line 52: The tests for the V1 dispatch path should be pinned to the concrete
GROVE_V3 constant rather than calling GroveVersion::latest(); update the import
and usages accordingly: change the use line to import GROVE_V3 (replace any
GROVE_V2 import) and replace calls to GroveVersion::latest() in the test
functions (e.g., test_path_query_proofs_without_subquery_with_reference,
test_path_query_proofs_without_subquery and related prove_query_non_serialized
tests) with the GROVE_V3 symbol so the tests deterministically target the
V1-proof dispatch path; also scan the nearby test ranges noted (around the given
function blocks) and replace any other latest() occurrences with GROVE_V3.

In `@grovedb/src/tests/provable_count_sum_tree_tests.rs`:
- Around line 2656-2673: The helper check_v1_layer_proof currently returns early
on non-Merk ProofBytes (match arm `_ => return`), which skips recursing into
layer.lower_layers and can miss child Merk layers; change it so recursion always
descends into layer.lower_layers regardless of the ProofBytes variant and only
attempt to decode/collect ops when ProofBytes::Merk is present (i.e., match to
get merk_bytes and run Decoder there, but do not return from the function on
other variants), keeping the recursion over lower_layers after/independent of
the match; update check_v1_layer_proof (and any borrow usage) accordingly so
lower_layers are always traversed.

---

Nitpick comments:
In `@grovedb/src/tests/provable_count_tree_comprehensive_test.rs`:
- Around line 719-834: The test
test_provable_count_tree_proof_contains_count_nodes currently uses
GroveVersion::latest() but asserts on GroveDBProof::V1; replace the dynamic
latest() with the explicit V1 grove version constant (e.g., GroveVersion::V1 or
the project’s named V1 constant) where grove_version is set so the test is
pinned to the proof format that produces GroveDBProof::V1; update the
instantiation at the top of the test (the grove_version binding used in db
creation, db.insert calls, and prove_query) to use that explicit V1 constant
instead of GroveVersion::latest().

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 09acd401-11bc-4b91-bae9-d0aba362b002

📥 Commits

Reviewing files that changed from the base of the PR and between 2c1fc47 and faea2ca.

📒 Files selected for processing (15)
  • grovedb-version/src/lib.rs
  • grovedb-version/src/version/grovedb_versions.rs
  • grovedb-version/src/version/mod.rs
  • grovedb-version/src/version/v1.rs
  • grovedb-version/src/version/v2.rs
  • grovedb-version/src/version/v3.rs
  • grovedb/src/operations/proof/generate.rs
  • grovedb/src/tests/commitment_tree_tests.rs
  • grovedb/src/tests/dense_tree_tests.rs
  • grovedb/src/tests/mmr_tree_tests.rs
  • grovedb/src/tests/mod.rs
  • grovedb/src/tests/proof_coverage_tests.rs
  • grovedb/src/tests/provable_count_sum_tree_tests.rs
  • grovedb/src/tests/provable_count_tree_comprehensive_test.rs
  • grovedb/src/tests/v1_proof_tests.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • grovedb-version/src/version/mod.rs
  • grovedb-version/src/version/v2.rs
  • grovedb/src/tests/commitment_tree_tests.rs
  • grovedb/src/tests/proof_coverage_tests.rs

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reviewed

Comment thread grovedb/src/tests/mod.rs
};

use grovedb_version::version::GroveVersion;
use grovedb_version::version::{v2::GROVE_V2, GroveVersion};

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

no need

Query,
};
use grovedb_version::version::GroveVersion;
use grovedb_version::version::{v2::GROVE_V2, GroveVersion};

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

no need

@QuantumExplorer
QuantumExplorer merged commit cbf0c85 into develop Mar 6, 2026
9 of 10 checks passed
@QuantumExplorer
QuantumExplorer deleted the fix/A2-add-version-gating-to-proof-methods branch March 6, 2026 03:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants