Skip to content

fix(insert): restore v4.1.0 non-batch dispatch for count/sum trees (protocol-v11 consensus) - #757

Closed
shumkov wants to merge 1 commit into
developfrom
fix/v11-subtree-insert-dispatch-consensus
Closed

fix(insert): restore v4.1.0 non-batch dispatch for count/sum trees (protocol-v11 consensus)#757
shumkov wants to merge 1 commit into
developfrom
fix/v11-subtree-insert-dispatch-consensus

Conversation

@shumkov

@shumkov shumkov commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

What

Restore the grovedb v4.1.0 non-batch insert dispatch for CountSumTree, ProvableCountTree and ProvableCountSumTree so they are written via the plain-value path (Op::Put) instead of the layered-subtree path (Op::PutLayeredReference).

Why — consensus break at protocol v11

GroveDb::add_element_on_transaction (the non-batch insert path used by grove_insert_if_not_exists) decides whether an element is inserted as a layered subtree (Op::PutLayeredReference) or a plain value (Op::Put).

  • In v4.1.0 (86562f65), CountSumTree / ProvableCountTree / ProvableCountSumTree fall through to _ => element.insert()Op::Put.
  • PR feat: subtree dump/restore primitives #752 broadened the tree arm to include them → Op::PutLayeredReference.

The two ops compute a different parent-node value_hash:

Op::Put                : value_hash = value_hash(serialized)
Op::PutLayeredReference: value_hash = combine_hash(value_hash(serialized), child_root_hash)
                                    = combine_hash(value_hash(serialized), NULL_HASH)   // empty tree

That changes the parent node hash → the grovedb root. These tree types are created on the protocol-v11 activation (Dash Platform transition_to_version_11 inserts an empty_provable_count_sum_tree and an empty_count_sum_tree), so the broadened dispatch makes a v11 node compute a different app-hash than the v4.1.0-era binaries that produced the canonical chain — a consensus divergence on replay.

Evidence (testnet block 245,344, the v10→v11 activation block)

Replaying the block from the identical committed state, logging the grovedb root after each transition insert:

step op v4.1.0 (canonical) a18f792 (regressed)
00 committed v10 state 76d92e… 76d92e…
01 empty_sum_tree @ [56] 4d08e1c3… 4d08e1c3… (same — SumTree is in the tree arm in both)
02 empty_provable_count_sum_tree @ [56,'c'] e0f9eded… 9b2b19a9… (diverges)

Step 01 (SumTree, unchanged) is identical; step 02 (ProvableCountSumTree, which changed arms) diverges. All later steps diverge downstream.

The fix

In add_element_on_transaction, move CountSumTree / ProvableCountTree / ProvableCountSumTree out of the layered-subtree arm back to the Op::Put arm, exactly matching v4.1.0. Tree / SumTree / BigSumTree / CountTree keep the layered behavior (unchanged in both revs). The v12-only ProvableSumTree / ProvableCountProvableSumTree (and CommitmentTree / MmrTree / BulkAppendTree / DenseAppendOnlyFixedSizeTree) were never on consensus and keep their current behavior.

This is an accidental regression (PR #752 was "subtree dump/restore primitives"); the broadened behavior never reached consensus, so restoring v4.1.0 unconditionally is correct for all live protocol versions.

Test

provable_count_sum_tree_insert_preserves_v11_consensus_root pins the post-insert root so the dispatch cannot silently regress again (empty_sum_tree as the unchanged control).

Summary by CodeRabbit

  • Bug Fixes

    • Corrected consensus-critical behavior during tree insertion operations to ensure all element types are handled according to protocol specifications, preventing potential consensus rule violations.
  • Tests

    • Added comprehensive regression test for consensus behavior, validating root hash generation and protecting against future deviations from expected protocol compliance.

…rotocol-v11 consensus)

CountSumTree, ProvableCountTree and ProvableCountSumTree must be inserted via
the plain-value path (Op::Put), not the layered-subtree path
(Op::PutLayeredReference). PR #752 accidentally moved them into the layered arm
of add_element_on_transaction, which changes the parent node's value_hash from
value_hash(serialized) to combine_hash(value_hash(serialized), NULL_HASH) and
therefore the grovedb root -- breaking protocol-v11 consensus on replay
(testnet block 245,344: transition_to_version_11 inserts an
empty_provable_count_sum_tree and an empty_count_sum_tree).

Restores the grovedb v4.1.0 behavior frozen into the v11 activation chain.
The v12-only ProvableSumTree / ProvableCountProvableSumTree keep the layered
behavior (never on consensus). Adds a regression test pinning the root.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@shumkov
shumkov requested a review from QuantumExplorer as a code owner June 4, 2026 00:43
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR refactors consensus-critical element insertion routing in grovedb to explicitly separate how CountSumTree and Provable* variants are routed through the insertion logic, ensuring they use non-layered Op::Put insertion. A regression test validates the protocol v11 consensus root hash behavior.

Changes

Protocol v11 Consensus-Critical Tree Insertion Routing

Layer / File(s) Summary
Element insertion routing refactoring
grovedb/src/operations/insert/mod.rs
Reorganized the element.underlying() match in add_element_on_transaction to explicitly group Element::Tree/SumTree/BigSumTree/CountTree, and adjusted match arms so CountSumTree/ProvableCountTree/ProvableCountSumTree are routed through the element.insert(...) path alongside Item/SumItem/ItemWithSumItem, with added comments clarifying consensus-critical protocol v11/GROVE_V2 requirements for non-layered insertion.
Protocol v11 consensus regression test
grovedb/src/operations/insert/mod.rs
Added test provable_count_sum_tree_insert_preserves_v11_consensus_root that inserts control and provable tree variants, computes their root hashes, and asserts both match fixed golden byte arrays to pin v4.1.0 / protocol-v11 consensus behavior.

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • dashpay/grovedb#670: Wires the new Element::ProvableCountProvableSumTree variant into the provable-tree dispatch group that is being reshaped in this PR's consensus-critical insertion routing refactoring.

Poem

🐰 Tree variants now find their place,
Consensus routes trace protocol space,
Provable paths, golden hashes bright,
V11 behavior locked in light!

🚥 Pre-merge checks | ✅ 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 accurately describes the main change: restoring v4.1.0 non-batch dispatch for count/sum trees to fix protocol-v11 consensus behavior. It is specific, clear, and directly reflects the primary purpose of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/v11-subtree-insert-dispatch-consensus

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.

@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.

🧹 Nitpick comments (3)
grovedb/src/operations/insert/mod.rs (3)

629-630: 💤 Low value

Drop the eprintln! debug output.

These print on every test run and add noise to CI logs; the golden assert_eq! already reports the actual root on failure.

♻️ Proposed cleanup
-        eprintln!("root_1 (control sum_tree)            = {root_1:?}");
-        eprintln!("root_2 (provable_count_sum_tree ins) = {root_2:?}");
-
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@grovedb/src/operations/insert/mod.rs` around lines 629 - 630, Remove the
temporary debug prints that print roots to stderr: delete the two eprintln!
calls that reference root_1 and root_2 (the lines printing "root_1 (control
sum_tree) = {root_1:?}" and "root_2 (provable_count_sum_tree ins) =
{root_2:?}"). Keep the assertion(s) intact (the existing assert_eq! that
compares the roots) so failures still report values via the test framework.

288-300: 💤 Low value

Consider relocating the consensus-critical note to the Item arm.

This block documents CountSumTree/ProvableCountTree/ProvableCountSumTree, but those variants are routed in the Item arm (Lines 359-374), not in the layered arm immediately below it. There is already a (shorter) note at Lines 356-358. A reader scanning the layered arm sees a long note about variants that aren't there. Either move the detailed rationale down to the Item arm or trim this block to a one-line pointer to keep the routing self-explanatory.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@grovedb/src/operations/insert/mod.rs` around lines 288 - 300, Move or trim
the long consensus-critical comment so it sits next to the actual insertion code
path for those variants: either relocate the detailed paragraph from the
layered-subtree arm to the `Item` arm (the match arm handling `Op::Put` / `Item`
where `CountSumTree`, `ProvableCountTree`, and `ProvableCountSumTree` are
routed), or replace the layered-arm block with a single-line pointer referencing
the full rationale in the `Item` arm; ensure references to the specific variants
(`CountSumTree`, `ProvableCountTree`, `ProvableCountSumTree`) and the reason
they must go through the plain-value `Item`/`Op::Put` path remain adjacent to
the `Item` arm handling insertion.

597-648: ⚡ Quick win

Extend the regression guard to the other two consensus-critical trees.

This PR changes non-batch dispatch for three types — CountSumTree, ProvableCountTree, and ProvableCountSumTree — and the comment at Lines 587-588 calls out both empty_provable_count_sum_tree (CLEAR_ADDRESS_POOL) and empty_count_sum_tree (ADDRESS_BALANCES) as v11 insertions. The test only pins ProvableCountSumTree, leaving CountSumTree and ProvableCountTree routing unguarded against a future re-regression to the layered path. Adding the golden root for at least empty_count_sum_tree would close that gap.

Want me to draft the additional empty_count_sum_tree / ProvableCountTree assertions (you'd fill in the golden hashes from a passing run)?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@grovedb/src/operations/insert/mod.rs` around lines 597 - 648, The test
currently only asserts the golden root for empty_provable_count_sum_tree; extend
the regression guard by inserting and asserting golden roots for the other two
consensus-critical trees: call db.insert with Element::empty_count_sum_tree()
(ADDRESS_BALANCES) and Element::empty_provable_count_tree() (CLEAR_ADDRESS_POOL
variant) similar to the existing provable case, capture their root hashes (e.g.,
root_count_sum, root_provable_count), and add assert_eq! checks against new
GOLDEN_* constants; update the test function
provable_count_sum_tree_insert_preserves_v11_consensus_root to include these
additional insertions and assertions so CountSumTree and ProvableCountTree
routing cannot regress to layered path behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@grovedb/src/operations/insert/mod.rs`:
- Around line 629-630: Remove the temporary debug prints that print roots to
stderr: delete the two eprintln! calls that reference root_1 and root_2 (the
lines printing "root_1 (control sum_tree) = {root_1:?}" and "root_2
(provable_count_sum_tree ins) = {root_2:?}"). Keep the assertion(s) intact (the
existing assert_eq! that compares the roots) so failures still report values via
the test framework.
- Around line 288-300: Move or trim the long consensus-critical comment so it
sits next to the actual insertion code path for those variants: either relocate
the detailed paragraph from the layered-subtree arm to the `Item` arm (the match
arm handling `Op::Put` / `Item` where `CountSumTree`, `ProvableCountTree`, and
`ProvableCountSumTree` are routed), or replace the layered-arm block with a
single-line pointer referencing the full rationale in the `Item` arm; ensure
references to the specific variants (`CountSumTree`, `ProvableCountTree`,
`ProvableCountSumTree`) and the reason they must go through the plain-value
`Item`/`Op::Put` path remain adjacent to the `Item` arm handling insertion.
- Around line 597-648: The test currently only asserts the golden root for
empty_provable_count_sum_tree; extend the regression guard by inserting and
asserting golden roots for the other two consensus-critical trees: call
db.insert with Element::empty_count_sum_tree() (ADDRESS_BALANCES) and
Element::empty_provable_count_tree() (CLEAR_ADDRESS_POOL variant) similar to the
existing provable case, capture their root hashes (e.g., root_count_sum,
root_provable_count), and add assert_eq! checks against new GOLDEN_* constants;
update the test function
provable_count_sum_tree_insert_preserves_v11_consensus_root to include these
additional insertions and assertions so CountSumTree and ProvableCountTree
routing cannot regress to layered path behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3f11de2f-66e5-4062-b3f5-8b2633043f14

📥 Commits

Reviewing files that changed from the base of the PR and between a18f792 and e5a904d.

📒 Files selected for processing (1)
  • grovedb/src/operations/insert/mod.rs

shumkov pushed a commit that referenced this pull request Jun 4, 2026
…#759)

* fix(insert): version-gate add_element_on_transaction (v0=Op::Put, v1=layered)

PR #752 broadened the non-batch insert tree arm so CountSumTree /
ProvableCountTree / ProvableCountSumTree are written as layered subtrees
(Op::PutLayeredReference). grovedb <= v4.1.0 wrote them as plain values
(Op::Put), and that is the behaviour frozen into the live protocol-v11
activation chain (testnet block 245,344, transition_to_version_11). The
layered op computes a different parent value_hash —
combine_hash(value_hash(serialized), NULL_HASH) instead of
value_hash(serialized) — and therefore a different grovedb root, a consensus
divergence on replay.

Rather than revert unconditionally (cf. #757), split add_element_on_transaction
into a versioned dispatch, mirroring the proof v0/v1 pattern:

- v0: grovedb v4.1.0 behaviour — those three types take the Op::Put arm.
  Selected by GROVE_V1 / GROVE_V2, preserving the protocol-v11 root.
- v1: current behaviour — those three types are layered, consistent with the
  batch insert path (both root hash and fee). Selected by GROVE_V3.

v0/v1 live in their own files as frozen snapshots; they differ only in which
match arm those three element types fall into. GROVE_V3's
add_element_on_transaction version slot is bumped 0 -> 1; v1/v2 stay 0.

Adds a consensus-guard test that replays the transition_to_version_11 shape and
pins both roots: the v0 root is byte-identical to PR #757's protocol-v11 golden,
and the v1 (layered) root differs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(insert): cover all add_element_on_transaction arms under v0 and v1

Adds exhaustive non-batch-insert coverage for both frozen snapshots
(v0.rs / v1.rs): the layered-tree arm (every tree type), the commitment-tree
arm, the append-tree arm (MMR / bulk-append / dense), the item arm, the
reference arm, both override guards, and the empty-tree-only (value.is_some)
guard — driven under GROVE_V1 (v0 / Op::Put) and GROVE_V3 (v1 / layered).
Also asserts the dispatcher rejects an unknown version slot.

Closes the codecov/patch gap on the new add_element_on_transaction module.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shumkov

shumkov commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Closed in favor of #759

@shumkov shumkov closed this Jun 4, 2026
@shumkov
shumkov deleted the fix/v11-subtree-insert-dispatch-consensus branch June 4, 2026 07:34
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.

1 participant