Skip to content

fix(drive): unify shielded pool genesis/upgrade construction to prevent state divergence#3801

Merged
QuantumExplorer merged 2 commits into
v3.1-devfrom
fix/shielded-pool-genesis-upgrade-divergence
Jun 5, 2026
Merged

fix(drive): unify shielded pool genesis/upgrade construction to prevent state divergence#3801
QuantumExplorer merged 2 commits into
v3.1-devfrom
fix/shielded-pool-genesis-upgrade-divergence

Conversation

@shumkov

@shumkov shumkov commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

⚠️ CONSENSUS-CRITICAL — requires careful review before merge. This changes how the v12 shielded-pool GroveDB subtree is constructed at genesis. v12 is not yet activated, so there is no live-network fork today, but this touches state-machine construction and must be reviewed as consensus code.

Issue being fixed or feature implemented

The v12 shielded pool [ShieldedBalances] subtree was built two different ways that were NOT byte-identical:

  • Genesis path (Drive::create_initial_state_structure_v3initial_state_structure_shielded_pool_operations): the main pool [ShieldedBalances, "M"] and its eight children were added to a GroveDbOpBatch and applied via grove_apply_batch. GroveDB roots a sorted batch at its median key, so the pool's parent Merk was rooted at key [160].
  • Upgrade path (Platform::transition_to_version_12): the same elements were inserted one-by-one with sequential breadth-first grove_insert_if_not_exists calls, rooting the parent Merk at key [128] — the intended NOTES-at-root layout documented in drive::drive::shielded::paths (SHIELDED_NOTES_KEY = 128 sits at the root of the balanced pool tree).

AVL rebalancing is insertion-order-sensitive. The result was two different [ShieldedBalances] subtree root hashes for the same logical structure:

  • A fresh-genesis-v12 node (state-synced / built from genesis at protocol v12) produced the [160] layout.
  • An in-place-upgraded node (already on v11, ran the transition at the activation block) produced the [128] layout.

These two node populations would compute different app hashes at the v11→v12 boundary block and fork.

Why it's consensus-relevant

  • The genesis ≡ upgrade invariant — a chain born at v12 must hold the same state as a chain that upgraded into v12 — was broken for the shielded pool.
  • A fresh-genesis-v12 node and an in-place-upgraded v12 node would disagree on the application hash.
  • No live mainnet/testnet fork: v12 is not yet activated. The live network will upgrade in place via the sequential path, which already produces the correct [128] layout — i.e. the live upgrade converges on the right shape. The bug is in the genesis path ([160]), which violates the documented design and the genesis≡upgrade invariant; it would surface for any node that state-syncs or builds a fresh v12 chain.

The design intent (packages/rs-drive/src/drive/shielded/paths.rs) is NOTES at the root ([128]). The sequential/upgrade path matches the design; the genesis/batch path ([160]) was the wrong one.

What was done?

Introduced one shared sequential builder and routed both construction paths through it, so the pool is built identically by construction:

  • New helper Drive::insert_shielded_pool_structure (packages/rs-drive/src/drive/shielded/insert_shielded_pool_structure.rs): inserts the main pool [ShieldedBalances, "M"] SumTree, then the eight children in the exact breadth-first order (NOTES 128, NULLIFIERS 64, ANCHORS_IN_POOL 192, TOTAL_BALANCE 32, ANCHORS_BY_HEIGHT 96, RECENT_NULLIFIERS 160, COMPACTED_NULLIFIERS 224, EXPIRATION 240), each via a sequential grove_insert_if_not_exists. The element types are copied verbatim from the authoritative transition_to_version_12 body.
  • transition_to_version_12: keeps its top-level [ShieldedBalances] SumTree insert and now delegates the pool + eight children to the shared helper. Behavior is unchanged (it already produced [128]).
  • create_initial_state_structure_v3: keeps creating the top-level [ShieldedBalances] SumTree as a standalone insert (already verified identical between paths), removes the shielded ops from the GroveDbOpBatch (deleted initial_state_structure_shielded_pool_operations), and calls the shared helper after grove_apply_batch (so [ShieldedBalances] exists first). Genesis now builds the pool sequentially → [128], matching the upgrade.
  • Updated/removed now-stale doc comments; no comment claims the batch roots NOTES at [128] anymore.

v11 is intentionally NOT modified. The same batch-vs-sequential pattern exists at the v10→v11 boundary (create_initial_state_structure_v2 vs transition_to_version_11), but v11 is already activated on mainnet/testnet — changing how v11 builds trees would itself fork the live network. Instead, a read-only equivalence test confirms the two v11 paths already coincide.

Files changed

  • packages/rs-drive/src/drive/shielded/insert_shielded_pool_structure.rs (new — shared builder)
  • packages/rs-drive/src/drive/shielded/mod.rs (register module)
  • packages/rs-drive/src/drive/initialization/v3/mod.rs (genesis: drop batch pool ops, call shared helper after batch apply)
  • packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs (transition delegates to shared helper; new equivalence tests + shared collect_subtree_diffs helper)

How Has This Been Tested?

Two new tests drive the real production functions (not a hand-rebuilt batch):

test_genesis_v12_and_upgrade_to_v12_build_identical_shielded_pool (consensus guard)

  • Platform A = real genesis-v12 (with_initial_protocol_version(12).set_genesis_state() → real create_initial_state_structure_v3).
  • Platform B = real genesis-v11 then real transition_to_version_12.
  • Asserts the entire [ShieldedBalances] subtree is byte-identical at every depth (main pool element with its carried root_key, all eight children, and everything below), via a recursive collect_subtree_diffs helper.
  • RED → GREEN demonstrated:
    • Against the unfixed genesis (batch): GENESIS pool root [hex: a0] (160)UPGRADE [hex: 80] (128)FAILED (path=["34"] key=4d).
    • With the fix: both [hex: 80] (128)ok.

test_genesis_v11_and_upgrade_to_v11_build_identical_address_trees (read-only v11 tripwire)

  • Platform A = real genesis-v11; Platform B = real genesis-v10 then real transition_to_version_11.
  • Asserts the SavedBlockTransactions (3 children — the batch-vs-sequential risk) and AddressBalances subtrees are byte-identical. GREEN as-is — the two v11 paths coincidentally already match. The test is documented as a STOP signal: if it ever goes RED, do not edit v11 code (it's live), analyze the discrepancy.

Note on test design: both tests compare the named subtree, not the whole-DB root hash. The whole DB also carries the genesis epoch's recorded protocol-version field ([Pools, epoch_0, "v"] = 12 vs 11, or 11 vs 10), which legitimately differs between a chain born at vN and one born at v(N-1) then upgraded — that field is unrelated to subtree construction and must not pollute the equivalence check. A recursive diagnostic confirmed the shielded subtree was the only construction-driven divergence.

Full surrounding suites pass:

  • cargo test -p drive-abci --lib perform_events_on_first_block_of_protocol_change13 passed.
  • cargo test -p drive --lib initialization8 passed.
  • cargo clippy -p drive --lib and cargo clippy -p drive-abci --lib --tests → clean (no new warnings).

No golden/pinned genesis-v12 or shielded root-hash constants exist anywhere in the tree, so nothing else needed updating.

Breaking Changes

No API breaking changes. This is a state-construction change to a not-yet-activated protocol version (v12 genesis). It makes a fresh-genesis-v12 chain's [ShieldedBalances] subtree byte-identical to an upgraded one (the upgrade shape, [128], is unchanged). v11 construction is untouched, so already-activated networks are unaffected.

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

Summary by CodeRabbit

  • Tests

    • Added boundary equivalence tests and a recursive subtree-diff helper to assert byte‑identical shielded balance and related subtrees between genesis and upgrade paths, preventing consensus divergence.
  • Refactor

    • Consolidated shielded-pool initialization into a shared routine used by both genesis and upgrade flows, replacing manual inlined construction and ensuring consistent, deterministic database shape.

…nt state divergence

CONSENSUS-CRITICAL. The v12 shielded pool [ShieldedBalances] subtree was
built two different ways that were NOT byte-identical:

- Genesis path (create_initial_state_structure_v3 →
  initial_state_structure_shielded_pool_operations) built the pool and its
  eight children via a sorted GroveDbOpBatch. GroveDB roots a sorted batch at
  its median key, so the pool's parent Merk was rooted at [160].
- Upgrade path (Platform::transition_to_version_12) built the same elements
  with sequential breadth-first grove_insert_if_not_exists calls, rooting the
  parent Merk at [128] — the intended NOTES-at-root layout documented in
  drive::drive::shielded::paths.

A state-synced fresh-genesis-v12 node (160) and an in-place-upgraded v12 node
(128) therefore produced different [ShieldedBalances] subtree root hashes and
would have disagreed on the app hash at the v11→v12 boundary block. v12 is not
yet activated, so no live network has forked; the live mainnet/testnet upgrade
converges on the correct sequential [128] layout — but the invariant that
genesis and upgrade build identical state was broken.

Fix: introduce one shared sequential builder,
Drive::insert_shielded_pool_structure, and call it from BOTH paths:
- transition_to_version_12 now delegates the pool + eight children to it
  (behavior unchanged: still [128]).
- create_initial_state_structure_v3 removes the shielded ops from the batch and
  calls the shared helper AFTER the batch apply (so the top-level
  [ShieldedBalances] SumTree exists first). Genesis now builds the pool
  sequentially → [128], matching the upgrade by construction.

v11 is intentionally NOT touched. The same batch-vs-sequential pattern exists at
the v10→v11 boundary, but v11 is already activated on mainnet/testnet, so
changing its construction would itself fork the live network.

Tests (RED→GREEN, driving the REAL production functions):
- test_genesis_v12_and_upgrade_to_v12_build_identical_shielded_pool: genesis-v12
  vs genesis-v11+transition_to_version_12, asserts the entire [ShieldedBalances]
  subtree is byte-identical at every depth. Would have caught this in CI:
  ✖ before fix (genesis pool root [160] ≠ upgrade [128]), ✔ after (both [128]).
- test_genesis_v11_and_upgrade_to_v11_build_identical_address_trees: read-only
  tripwire confirming the v11 SavedBlockTransactions/AddressBalances subtrees
  already coincide across batch (genesis) and sequential (upgrade) paths. GREEN.

The tests compare the named subtree (not the whole-DB root hash) on purpose: the
whole DB also carries the genesis epoch's recorded protocol-version field, which
legitimately differs between a chain born at vN and one born at v(N-1) then
upgraded, and is unrelated to subtree construction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shumkov shumkov requested a review from QuantumExplorer as a code owner June 5, 2026 11:59
@thepastaclaw

thepastaclaw commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

✅ Review complete (commit acb41a6)

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a45036ba-e763-4915-b945-4c22a5b00bdc

📥 Commits

Reviewing files that changed from the base of the PR and between 4979fe4 and acb41a6.

📒 Files selected for processing (1)
  • packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs

📝 Walkthrough

Walkthrough

Centralizes ShieldedBalances subtree construction into Drive::insert_shielded_pool_structure, updates genesis (v3) and upgrade (v12) flows to call it, and adds recursive subtree-diff tests that assert byte-identical GroveDB subtrees across genesis and upgrade paths.

Changes

Shared Shielded Pool Initialization for Genesis and Upgrade

Layer / File(s) Summary
Module wiring and insert_shielded_pool_structure implementation
packages/rs-drive/src/drive/shielded/mod.rs, packages/rs-drive/src/drive/shielded/insert_shielded_pool_structure.rs
New insert_shielded_pool_structure submodule and public Drive::insert_shielded_pool_structure method sequentially populate RootTree::ShieldedBalances with the main pool ("M") and child subtrees (notes, nullifiers, anchors, totals, recent nullifiers as NotSummed, compacted nullifiers, expiration-time) using breadth-first grove_insert_if_not_exists.
Genesis v3 initialization refactoring
packages/rs-drive/src/drive/initialization/v3/mod.rs
Reordered v3 initialization to apply the base GroveDB batch first, then call insert_shielded_pool_structure instead of building shielded-pool batch ops inline; imports narrowed.
Protocol upgrade v12 refactoring
packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs
transition_to_version_12 now creates the top-level ShieldedBalances SumTree separately and delegates child subtree construction to insert_shielded_pool_structure; removed now-unused imports and inlined insert logic.
Test infrastructure and boundary equivalence tests
packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs
Adds collect_subtree_diffs recursive comparator with depth cap and tests that assert byte-identical [ShieldedBalances] for v12 genesis vs v11→v12 upgrade, and byte-identical [SavedBlockTransactions] and [AddressBalances] for v11 genesis vs v10→v11 upgrade.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • dashpay/platform#3789: Related GroveDB insertion semantics and a v11 consensus regression test tied to grove_insert_if_not_exists.

Suggested reviewers

  • thepastaclaw
  • QuantumExplorer

Poem

🐰 I nibble bytes where branches meet,
One helper winds each ordered sheet,
Genesis and upgrade now agree,
Breadth-first roots in harmony,
Consensus carrots for you and me.

🚥 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 clearly and concisely summarizes the main change: unifying shielded pool genesis/upgrade construction to prevent state divergence, which is the core objective of the PR.
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/shielded-pool-genesis-upgrade-divergence

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.

Actionable comments posted: 1

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

Inline comments:
In
`@packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs`:
- Around line 703-718: The current read_level() masks grove_get_raw_path_query()
failures by using unwrap_or_default(), causing read errors to be treated as
empty maps and letting collect_subtree_diffs() report false "no diffs"; change
read_level() to propagate the error instead of defaulting: remove
unwrap_or_default() and return a Result (or an Option that signals failure) from
read_level(), propagate that Result up to callers (including the code that calls
collect_subtree_diffs()), and ensure callers handle the Err case by failing the
equivalence guard or recording the read error; update any signature references
to read_level(), and ensure grove_get_raw_path_query() errors (from
QueryResultType usage) are surfaced rather than converted into {}.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 974fc056-37bd-409d-8a75-5a8039e8c0af

📥 Commits

Reviewing files that changed from the base of the PR and between 8d60d3f and 4979fe4.

📒 Files selected for processing (4)
  • packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs
  • packages/rs-drive/src/drive/initialization/v3/mod.rs
  • packages/rs-drive/src/drive/shielded/insert_shielded_pool_structure.rs
  • packages/rs-drive/src/drive/shielded/mod.rs

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review

PR is a well-scoped consensus-divergence fix: a single shared sequential builder is now used for both the v12 genesis path and the v11→v12 upgrade path, with byte-equivalence tests pinning the invariant. The only verified in-scope issue is a test-quality concern — the equivalence helper read_level swallows GroveDB query errors via unwrap_or_default(), which could mask future regressions as false greens. No blocking issues; production code paths look correct.

🟡 1 suggestion(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs:703-718: `read_level` swallows GroveDB query errors, weakening the equivalence guard
  `read_level` folds any failure from `grove_get_raw_path_query` into an empty `BTreeMap` via `.unwrap_or_default()`. This is the sole observation primitive driving `collect_subtree_diffs`, which backs `test_genesis_v12_and_upgrade_to_v12_build_identical_shielded_pool` and the v11 tripwire — tests whose entire purpose is to catch consensus-forking state divergence. If a query errors symmetrically on both sides (path-shape regression, stub change, future version-routing bug), both maps become `{}`, no diffs are recorded, and the tests pass green while observing nothing. Distinguish a legitimately empty subtree from a real `Err(_)` by panicking (or recording an explicit diff) on the error case.

Comment on lines +703 to +718
platform
.drive
.grove_get_raw_path_query(
&pq,
txn,
QueryResultType::QueryKeyElementPairResultType,
&mut vec![],
&platform
.state
.load()
.current_platform_version()
.expect("platform version")
.drive,
)
.map(|(r, _)| r.to_key_elements().into_iter().collect())
.unwrap_or_default()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: read_level swallows GroveDB query errors, weakening the equivalence guard

read_level folds any failure from grove_get_raw_path_query into an empty BTreeMap via .unwrap_or_default(). This is the sole observation primitive driving collect_subtree_diffs, which backs test_genesis_v12_and_upgrade_to_v12_build_identical_shielded_pool and the v11 tripwire — tests whose entire purpose is to catch consensus-forking state divergence. If a query errors symmetrically on both sides (path-shape regression, stub change, future version-routing bug), both maps become {}, no diffs are recorded, and the tests pass green while observing nothing. Distinguish a legitimately empty subtree from a real Err(_) by panicking (or recording an explicit diff) on the error case.

Suggested change
platform
.drive
.grove_get_raw_path_query(
&pq,
txn,
QueryResultType::QueryKeyElementPairResultType,
&mut vec![],
&platform
.state
.load()
.current_platform_version()
.expect("platform version")
.drive,
)
.map(|(r, _)| r.to_key_elements().into_iter().collect())
.unwrap_or_default()
)
.map(|(r, _)| r.to_key_elements().into_iter().collect())
.unwrap_or_else(|e| panic!("failed to read subtree at {:?}: {e}", path))

source: ['claude', 'coderabbit']

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Resolved in acb41a6read_level swallows GroveDB query errors, weakening the equivalence guard no longer present.

Auto-resolved by the review system based on the latest commit diff. If you believe this was closed in error, reopen the thread.

@codecov

codecov Bot commented Jun 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.69863% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.19%. Comparing base (8d60d3f) to head (acb41a6).
⚠️ Report is 1 commits behind head on v3.1-dev.

Files with missing lines Patch % Lines
...events_on_first_block_of_protocol_change/v0/mod.rs 88.51% 24 Missing ⚠️
...c/drive/shielded/insert_shielded_pool_structure.rs 89.02% 9 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           v3.1-dev    #3801      +/-   ##
============================================
- Coverage     87.19%   87.19%   -0.01%     
============================================
  Files          2624     2625       +1     
  Lines        321186   321360     +174     
============================================
+ Hits         280046   280196     +150     
- Misses        41140    41164      +24     
Components Coverage Δ
dpp 87.73% <ø> (ø)
drive 86.05% <89.15%> (-0.01%) ⬇️
drive-abci 89.54% <88.51%> (-0.01%) ⬇️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.17% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 47.85% <ø> (ø)
🚀 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.

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

✅ DashSDKFFI.xcframework built for this PR.

SwiftPM (host the zip at a stable URL, then use):

.binaryTarget(
  name: "DashSDKFFI",
  url: "https://your.cdn.example/DashSDKFFI.xcframework.zip",
  checksum: "b5b9cd06ab0f7c645fae9c2836639bfb87567f7a76fbf9049d5deefefca2d719"
)

Xcode manual integration:

  • Download 'DashSDKFFI.xcframework' artifact from the run link above.
  • Drag it into your app target (Frameworks, Libraries & Embedded Content) and set Embed & Sign.
  • If using the Swift wrapper package, point its binaryTarget to the xcframework location or add the package and place the xcframework at the expected path.

…read errors

Address review feedback (@coderabbitai, @thepastaclaw): the test-only
`read_level` helper folded any `grove_get_raw_path_query` failure into an empty
`BTreeMap` via `unwrap_or_default()`. That could let `collect_subtree_diffs`
report "no diffs" when one side's subtree was actually unreadable — a false
GREEN. Now a query *error* panics the equivalence guard loudly, while a
legitimately empty (Ok) subtree still yields an empty map. Both equivalence
tests remain GREEN.

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

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review

Cumulative review: the sole prior finding (read_level swallowing GroveDB errors) is FIXED at packages/rs-drive-abci/.../v0/mod.rs:703-728 — it now panics on Err while preserving the legitimate empty-Ok case. New latest-delta finding from codex (test-coverage): collect_subtree_diffs reads each platform with its own state.current_platform_version, but platform_b's state remains v11 after transition_to_version_12, so the two sides of the v12 equivalence guard are queried under different drive/grove versions (v12 introduces GROVE_V3). Verified against the source; valid in-scope test-quality suggestion. No blocking issues; production code from prior reviewed SHA is unchanged.

🟡 1 suggestion(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs:695-729: Equivalence guard reads each platform under its own (stale) platform version
  `read_level` derives the drive version from `platform.state.load().current_platform_version()` for each side. Platform A is built at v12 (`with_initial_protocol_version(12)`) so reads use v12's drive/grove version, but Platform B is built at v11 and `transition_to_version_12` writes to grovedb without bumping `state.current_platform_version`. As a result the upgraded tree is queried with the v11 drive/grove version even though v12 moves Drive to GROVE_V3. The guard is meant to assert that genesis-v12 and upgraded-v12 trees are byte-identical under the target activation version, so both reads should be performed under the same target `PlatformVersion`. Thread the target `PlatformVersion` (the `platform_version_12` already available in the test) into `collect_subtree_diffs`/`read_level` and use its `drive` for both sides instead of pulling it from each platform's in-memory state. The same concern applies to the v11 tripwire test, which mixes a v10-state platform with a v11-state platform.

Comment on lines +695 to +729
fn read_level(
platform: &crate::platform_types::platform::Platform<crate::rpc::core::MockCoreRPCLike>,
txn: drive::grovedb::TransactionArg,
path: &[Vec<u8>],
) -> std::collections::BTreeMap<Vec<u8>, Element> {
let mut q = Query::new();
q.insert_all();
let pq = PathQuery::new(path.to_vec(), SizedQuery::new(q, None, None));
// A *query error* (as opposed to a legitimately empty subtree, which
// returns Ok with no items) must fail the equivalence guard loudly —
// swallowing it into an empty map could let `collect_subtree_diffs`
// report "no diffs" when one side was actually unreadable, producing
// a false GREEN. An empty Ok result correctly yields an empty map.
let (results, _) = platform
.drive
.grove_get_raw_path_query(
&pq,
txn,
QueryResultType::QueryKeyElementPairResultType,
&mut vec![],
&platform
.state
.load()
.current_platform_version()
.expect("platform version")
.drive,
)
.unwrap_or_else(|e| {
panic!(
"equivalence guard: subtree read at path {:?} must succeed, got: {e}",
path.iter().map(hex::encode).collect::<Vec<_>>()
)
});
results.to_key_elements().into_iter().collect()
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: Equivalence guard reads each platform under its own (stale) platform version

read_level derives the drive version from platform.state.load().current_platform_version() for each side. Platform A is built at v12 (with_initial_protocol_version(12)) so reads use v12's drive/grove version, but Platform B is built at v11 and transition_to_version_12 writes to grovedb without bumping state.current_platform_version. As a result the upgraded tree is queried with the v11 drive/grove version even though v12 moves Drive to GROVE_V3. The guard is meant to assert that genesis-v12 and upgraded-v12 trees are byte-identical under the target activation version, so both reads should be performed under the same target PlatformVersion. Thread the target PlatformVersion (the platform_version_12 already available in the test) into collect_subtree_diffs/read_level and use its drive for both sides instead of pulling it from each platform's in-memory state. The same concern applies to the v11 tripwire test, which mixes a v10-state platform with a v11-state platform.

source: ['codex']

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

While I might not have done things this way, it's fine.

@QuantumExplorer QuantumExplorer merged commit def390c into v3.1-dev Jun 5, 2026
21 checks passed
@QuantumExplorer QuantumExplorer deleted the fix/shielded-pool-genesis-upgrade-divergence branch June 5, 2026 15:33
@QuantumExplorer QuantumExplorer added this to the v4.0.0 milestone Jun 7, 2026
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.

3 participants