Skip to content

fix(batch): refuse an empty path on every reference hop instead of panicking - #1016

Merged
QuantumExplorer merged 1 commit into
developfrom
fix/batch-empty-reference-hop-panic
Oct 11, 2026
Merged

QuantumExplorer merged 1 commit into
developfrom
fix/batch-empty-reference-hop-panic

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

Issue

A crafted batch panics inside apply_batch instead of returning an error. This was on develop before #1015.

apply_batch refuses an empty reference path only for the reference op it is processing: "attempting to insert an empty reference" in the insert arm, and "attempting to refresh an empty reference" in the refresh arm. Ops run deepest level first, and keys within a path run in byte order. So an earlier op can follow its chain into a later pending reference before that op's own check runs.

follow_reference_get_value_hash resolves the next hop from the pending element with path_from_reference_qualified_path_type. That returns [] for AbsolutePathReference(vec![]), UpstreamRootHeightReference(0, vec![]) and UpstreamFromElementHeightReference(path.len(), vec![]). The function recursed with that [] without checking it. ops_by_qualified_paths.get(&[]) is always None, so the call went to process_reference(&[]). There qualified_path.split_last().expect("path validated non-empty above") panicked.

Reproducer, under [TEST_LEAF] in one batch:

insert_or_replace_op([TEST_LEAF], b"a", Element::new_reference(SiblingReference(b"b".to_vec())))
insert_or_replace_op([TEST_LEAF], b"b", Element::new_reference(AbsolutePathReference(vec![])))

A trusted RefreshReference on b that carries an empty path reaches the same expect, through process_reference's in-batch branch. On unmodified develop both new tests panic at grovedb/src/batch/mod.rs:2294 with path validated non-empty above.

Fix

follow_reference_get_value_hash now refuses an empty qualified_path with InvalidBatchOperation("reference chain resolves to an empty path"). Every hop goes through this function: the two top-level apply arms call it, and so does every recursive hop (the pending-reference arms, the bidirectional and backward-references arms, process_reference's refresh branch, and process_reference_with_hop_count_greater_than_one). One check therefore covers every hop.

process_reference_with_hop_count_greater_than_one needs no check of its own. It receives key and reference_path from process_reference, which has already split a non-empty path. Its only recursion goes back through follow_reference_get_value_hash. The two split_last().expect(..) calls stay in place, and their messages now name the check they depend on.

The check comes after the recursions_allowed == 0 → ReferenceLimit check. A chain that runs out of hops on an empty path still reports ReferenceLimit. Only runs that used to panic now behave differently.

Why this is not version-gated

The repo rule is that GROVE_V3 is live, so behavior-changing fixes go behind GROVE_V4+. This fix changes behavior only for batches that used to panic:

  • An empty qualified_path that gets past the hop-limit check always reached process_reference(&[]) and panicked. No pending op has an empty qualified path, since an op always has a key, so the in-batch lookup could never match.
  • A batch that panicked never committed at any grove version, so no stored state and no replayed block depends on how it failed. After the fix the batch returns an error and writes nothing. The tests check that the root hash is unchanged.
  • Batches that did not panic keep the same errors, hashes, costs and root hashes, because non-empty paths never reach the new check.

A version gate would only keep a panic alive on older versions, so the fix applies at every grove version.

Tests

In grovedb/src/tests/batch_unit_tests.rs (Group 16), both tests run over every version in GROVE_VERSIONS and over all three empty-resolving path types:

  • test_batch_ref_hop_to_pending_empty_reference_errors covers a → b, where b is a pending InsertOrReplace or InsertWithKnownToNotAlreadyExist reference with an empty path. These go through two different match arms.
  • test_batch_ref_hop_to_trusted_refresh_with_empty_path_errors covers a → b, where b is a trusted RefreshReference that carries an empty path.

Each case checks for the new InvalidBatchOperation error, which confirms the case reached the empty hop, and checks that the root hash did not change.

  • cargo test -p grovedb --lib batch: 749 passed
  • cargo test -p grovedb: 3557 lib tests passed (8 ignored), plus every integration and doc-test binary
  • cargo fmt --all -- --check: clean
  • cargo clippy -p grovedb --all-targets -- -D warnings: no lints in the changed files

docs/audit-non-issues.md has no entry covering this.

🤖 Generated with Claude Code

PR Hygiene · 45872cd

  • Bots — coderabbitai ✓ · thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed
  • Build running
  • Approvals — you own every area touched; none needed

When every merge requirement is met, the PR Hygiene check passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.

Summary by CodeRabbit

  • Bug Fixes
    • Batch reference processing now rejects references that resolve to an empty path, returning InvalidBatchOperation without writing changes.
    • The rejection applies across Grove versions and to multiple batch operation types.

…nicking

apply_batch refused an empty reference path only for the op it was
processing. Keys in a path run in byte order, so an earlier op could
follow its chain into a later pending reference (or a trusted
RefreshReference) whose path resolves to `[]` before that op's own check
ran. follow_reference_get_value_hash then recursed with `[]` and reached
process_reference, whose split_last().expect(..) panicked.

follow_reference_get_value_hash now refuses an empty qualified path with
InvalidBatchOperation. Every hop passes through it, so one check covers
the pending-reference arms, the refresh branch of process_reference and
process_reference_with_hop_count_greater_than_one. The check sits after
the hop-limit check, so only runs that used to panic change.

Not version-gated: a batch that took this path never committed at any
grove version, so no stored state or replayed block depends on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: db48b1a9-5ef6-4f48-b24d-4737b58f0448

📥 Commits

Reviewing files that changed from the base of the PR and between 9791d27 and 45872cd.


📒 Files selected for processing (3)
  • CHANGELOG.md
  • grovedb/src/batch/mod.rs
  • grovedb/src/tests/batch_unit_tests.rs

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.



📝 Walkthrough

Walkthrough

Batch reference-chain resolution now returns InvalidBatchOperation when a hop resolves to an empty qualified path. Tests cover multiple reference paths and targets across Grove versions, and verify that the root hash remains unchanged.

Changes

Batch reference path validation

Layer / File(s) Summary
Reject empty paths in reference chains
grovedb/src/batch/mod.rs, grovedb/src/tests/batch_unit_tests.rs, CHANGELOG.md
Reference-chain resolution rejects an empty qualified path before cycle detection or path splitting. Tests cover sibling references and trusted RefreshReference targets across Grove versions, and check that a rejected batch leaves the root hash unchanged. The changelog records the error behavior and version-gating details.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix


Merge Risk: ⚪ Minimal · up to 45872

No merge-blocking issue is identified. Merge after normal validation.

Pre-merge checks | Passed 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: rejecting empty paths on every reference hop to prevent panics.
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 2 files. (1 skipped: 1 …
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR


🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@github-actions

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Waiting for bot review — coderabbitai ✓ · thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Oct 11, 2026
@thepastaclaw

thepastaclaw commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

🔍 Review in progress — actively reviewing now (commit 45872cd) · triage: low · Phase 2 only (no Phase 1 for this repository)
████████░░░░░░░░░░░░ 42% · finishing up · running for 1 min
✅ triage → ⏭️ Phase 1 → ⏳ Phase 2 (3/4 lanes) → ▫️ verify 2 → ▫️ fresh Phase 2 → ▫️ fresh verify → ▫️ publish
Estimated from recent reviews of this tier · updated 08:15 UTC · live progress

@codecov

codecov Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.20%. Comparing base (9791d27) to head (45872cd).

Additional details and impacted files
@@           Coverage Diff            @@
##           develop    #1016   +/-   ##
========================================
  Coverage    93.20%   93.20%           
========================================
  Files          336      336           
  Lines       106238   106244    +6     
========================================
+ Hits         99018    99024    +6     
  Misses        7220     7220           
Components Coverage Δ
grovedb-core 91.57% <100.00%> (+<0.01%) ⬆️
merk 94.12% <ø> (ø)
storage 91.94% <ø> (ø)
commitment-tree 95.62% <ø> (ø)
mmr 95.43% <ø> (ø)
bulk-append-tree 93.72% <ø> (ø)
element 97.18% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@QuantumExplorer
QuantumExplorer merged commit 297cb88 into develop Oct 11, 2026
14 of 15 checks passed
@QuantumExplorer
QuantumExplorer deleted the fix/batch-empty-reference-hop-panic branch October 11, 2026 08:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-bots Waiting for the review bots to report on this head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants