Skip to content

fix(drive): keep cursor document in descending continuation-page proofs - #4541

Merged
QuantumExplorer merged 1 commit into
v4.2-devfrom
fix/desc-cursor-anchor-proof-starvation
Aug 30, 2026
Merged

fix(drive): keep cursor document in descending continuation-page proofs#4541
QuantumExplorer merged 1 commit into
v4.2-devfrom
fix/desc-cursor-anchor-proof-starvation

Conversation

@QuantumExplorer

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Fixes #4540.

On protocol v14, a documents query combining a startAfter/startAt cursor, a range clause on the terminal index property, and descending order returned a proof the client cannot verify:

grovedb: invalid proof: V1 proof is missing lower layer for non-empty tree at key 00

The rejection is correct — the cursor document is genuinely absent from the proof. The cursor fetch is merged into the proved query with limit + 1 (one slot reserved for the cursor document), and the prover spends that budget in root traversal order. grovedb's V4 merge propagates the inputs' direction onto the merged root, so a descending page visits the index branch before the cursor branch (key [0]); mid-timeline the index branch holds more than limit + 1 matches, exhausts the budget, and the prover omits the cursor subtree's lower layer. verify_start_at_document_in_proof then cannot extract the cursor document. This is the shape of every newest-first timeline continuation page, so any client paginating an [equality, $createdAt] index descending broke on page 2+.

Not reproducible on protocol ≤ v13: the V3 merge left the synthesized root ascending, which silently protected the cursor-first invariant.

What was done?

Pin the merged root back to ascending after PathQuery::merge in construct_path_query_operations, restoring the cursor-first traversal the reserved limit + 1 slot depends on.

  • Only the synthesized merged root flips: each input query lands intact inside a subquery branch of the merged query, so in-branch (newest-first) result order is preserved. Deliberately not done by flipping the inputs' roots before the merge — the main query's root is the terminal-property level and flipping it would prove the wrong (oldest-first) window.
  • The verifier never rebuilds the merged query — it runs the cursor and the main query as two separate subset queries — so the root's direction is not client-visible and no client change is needed. Proof bytes change only for the previously unverifiable shape (ascending pages already had an ascending root; equality-terminal descending pages force one; cursorless queries don't merge). At ≤ v13 the overwrite is a no-op, so no version gate is needed.
  • Corrected the comment that claimed the verifier rebuilds the merged query through this path.

Not addressed here (pre-existing, independent, noted in the issue): the cursor lowering excludes the cursor's entire terminal key, so documents tying the cursor's timestamp are skipped across page boundaries even when proofs verify.

How Has This Been Tested?

New round-trip regression test test_proved_desc_range_continuation_page_includes_cursor_document: non-unique [category, $createdAt] index, 12 documents under one category value, page 2 via startAfter (and the inclusive startAt variant) with a $createdAt > 0 range descending, proved with execute_with_proof_only_get_elements and checked against the unproved results. Without the fix the test fails with exactly the reported error; with it, both pages verify.

Also ran the full query_tests suite (56 tests), drive lib query/verify unit tests (711 + 270), query_tests_history, dashpay, and cargo clippy -p drive --all-targets — all green.

Breaking Changes

None. Proof generation only; no state, fee, or consensus change. Proof bytes change only for the query shape that currently produces an unverifiable proof.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

A documents query combining a startAfter/startAt cursor, a range clause
on the terminal index property, and descending order produced a proof
missing the cursor document's subtree layer, which no client can verify
("V1 proof is missing lower layer for non-empty tree at key 00").

The cursor fetch is merged into the proved query with limit + 1, one
slot reserved for the cursor document — a budget the prover spends in
root traversal order. Since grovedb's V4 merge propagates the inputs'
direction to the merged root, a descending page visits the index branch
before the cursor branch (key [0]), exhausts the budget mid-timeline,
and the prover omits the cursor subtree's lower layer.

Pin the merged root back to ascending after the merge so the cursor
branch spends its reserved slot first. Only the synthesized root flips —
each input query lands intact inside a subquery branch, preserving
in-branch (newest-first) result order — and the verifier never rebuilds
the merged query (it runs the cursor and main queries as separate subset
queries), so proof bytes change only for the previously unverifiable
shape and no client change is needed.

Fixes #4540

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.2.0 milestone Aug 30, 2026
@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 54 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0de145c8-0117-4b1a-b230-897a40645957

📥 Commits

Reviewing files that changed from the base of the PR and between a77d0d9 and 14cfca6.

📒 Files selected for processing (2)
  • packages/rs-drive/src/query/mod.rs
  • packages/rs-drive/tests/query_tests.rs

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.

@thepastaclaw

Copy link
Copy Markdown
Collaborator

🕓 Ready for review — 12 ahead in queue (commit 14cfca6)
Queue position: 13/13

@codecov

codecov Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 9.09091% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.68%. Comparing base (a77d0d9) to head (14cfca6).

Files with missing lines Patch % Lines
packages/rs-drive/src/query/mod.rs 9.09% 10 Missing ⚠️

❌ Your patch check has failed because the patch coverage (9.09%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##           v4.2-dev    #4541      +/-   ##
============================================
- Coverage     87.80%   87.68%   -0.13%     
============================================
  Files          2748     2748              
  Lines        355859   356325     +466     
============================================
- Hits         312472   312427      -45     
- Misses        43387    43898     +511     
Components Coverage Δ
dpp 89.04% <ø> (ø)
drive 86.24% <9.09%> (-0.30%) ⬇️
drive-abci 89.88% <ø> (+<0.01%) ⬆️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.92% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 48.64% <ø> (ø)
🚀 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.

@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

@QuantumExplorer
QuantumExplorer merged commit a7ccac2 into v4.2-dev Aug 30, 2026
18 of 19 checks passed
@QuantumExplorer
QuantumExplorer deleted the fix/desc-cursor-anchor-proof-starvation branch August 30, 2026 21:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants