Repository navigation
fix(sdk): query compacted address balances when the recent proof has no boundary - #5572
Claudius-Maginificent wants to merge 2 commits into
Conversation
…f has no boundary Extract the Phase 2 decision of incremental_catch_up into the pure needs_compacted_phase and test it with real, decodable recent proofs. Mock expectations return an empty proof, whose decode error takes the "run compacted" arm and hides the bug. Reproduces testnet SH-040: the trunk checkpoint predates a funding in a since-compacted block, and the RangeFrom recent proof holds a later block but no boundary, so the compacted phase is skipped and the credit is lost. The no-boundary test fails until the fix lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019H9THphMnuQyh9oPY13SoE
…no boundary Phase 2 of incremental catch-up read "no boundary in the recent proof" as "recent tree empty, nothing to compact" and skipped the compacted query. A RangeFrom proof (every first sync of a fresh wallet) does not carry its start key as a boundary, and a RangeAfter start key that was compacted away leaves none either. Either way, a credit that landed after the checkpoint in a since-compacted block was lost until the next full rescan (about 7 days by default). Seen on testnet as a 2.22e9-credit funding the wallet reported as 0. Run the compacted phase whenever the proof carries no boundary. The per-operation height pin in apply_op keeps compacted operations at or below the checkpoint, and recent blocks already inside a compacted range, from being counted twice. Cost: one extra compacted RPC in that case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019H9THphMnuQyh9oPY13SoE
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
|
✅ Final review complete — no blockers (commit ddbcda1) · triage: normal |
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Phase 1 + Phase 2
The change correctly treats a decodable recent proof without a boundary as inconclusive and conservatively runs the compacted phase. The new tests directly exercise the extracted production decision across boundary, no-boundary, preceding-boundary, and undecodable-proof cases; no in-scope correctness, security, architectural, or consensus issues were found. Supplied CI checks completed successfully.
Review provenance
Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
normalbygpt-6.1-sol(effort low) — This is a contained Rust client-side synchronization logic change affecting balance discovery and compacted/recent proof handling, so it requires substantive correctness review but does not modify a critical consensus, cryptographic, or funds-movement surface. - Phase 1 reviewers:
muse-spark-1.3-contributor— general (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— rust-quality (completed, effort xhigh); agentphase1-reviewer - Phase 1 model:
muse-spark-1.3-contributor— not quota-gated; passed overgemini-3.8-flash-high(antigravity quota lookup failed),glm-5.3-flash(not used above high effort; tier asks max) - Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort high); agentphase2-reviewer,gpt-6.1-sol— architecture-layering (completed, effort high); agentphase2-reviewer,gpt-6.1-sol— rust-quality (completed, effort high); agentphase2-reviewer,gpt-6.1-sol— security-auditor (completed, effort high); agentphase2-reviewer
Out-of-scope follow-up suggestions (1)
These are valid observations, but they are outside this PR's scope and should be handled in separate issues or author/maintainer-requested PRs rather than blocking this review.
- Infallible try_into uses unwrap in production path — Out of scope and pre-existing — the unwrap is in untouched code, is guarded by an explicit eight-byte length check, and is unrelated to the compacted-balance fix.
- Follow-up: Consider creating a separate issue or author/maintainer-requested PR for this.
Issue being fixed or feature implemented
A wallet's address balance sync could miss a credit and report the address as empty. This hits the first sync of every fresh wallet, and any sync whose recent-zone cursor has been compacted away. The credit stays lost until the next full rescan, which is about 7 days by default.
Seen on testnet in platform-wallet e2e SH-040. The trunk checkpoint was at 642131, and a 2.22e9-credit funding landed in a block in (642133, 642138]. Drive then compacted that block out of the recent zone, so the recent query returned only block 642139.
GetAddressInfoshowed 2.22e9, but the wallet saw 0 and shielding failed withPlatformShieldCapacityExceeded { available: 0 }.Root cause: Phase 2 of
incremental_catch_upread "the recent proof has no boundary node" as "the recent tree is empty, nothing to compact" and skipped the compacted query. That reading is wrong:RangeFromproof, used wheneverlast_known_recent_block == 0, does not carry its start key as a boundary.RangeAfterstart key that was compacted away leaves no boundary either.In both cases the blocks since the checkpoint may be sitting in the compacted tree. The function's own doc already said the compacted phase "always runs" for
RangeFrom, but the code did not do that.What was done?
Ok(None)fromget_last_recent_block_from_proofnow runs the compacted phase. The misleading comments and doc lines are corrected.needs_compacted_phase(recent_proof, checkpoint_height)so it can be unit-tested with real proofs. SDK mock expectations always return an emptyProof, which fails to decode and takes the "run compacted to be safe" arm, so a mock-driven sync test could never reach this bug.cargo test -p dash-sdk --libonv5.1-dev), needed to run these tests.Double counting is still prevented by the existing per-operation height pin in
apply_op, already covered byapply_block_changes_height_filter_drops_below_cursorandapply_block_changes_drops_recent_delta_already_inside_pinned_absolute:Cost: one extra compacted RPC per sync, only when the recent proof has no boundary.
This is client-side code only. No consensus path, versioned method or version table is touched.
How Has This Been Tested?
New unit tests for
needs_compacted_phaseinpackages/rs-sdk/src/platform/address_sync/mod.rs. They build real, decodable GroveDB proofs for the recent-tree layer:should_run_compacted_phase_when_recent_proof_has_no_boundary: the SH-040 shape (only block 642139, checkpoint 642131) and a genuinely empty recent zone. Both assert the proof decodes toOk(None). Failed before the fix; passes after.should_skip_compacted_phase_when_a_boundary_reaches_the_checkpoint: boundary equal to or above the checkpoint skips compacted.should_run_compacted_phase_when_the_boundary_predates_the_checkpointshould_run_compacted_phase_when_recent_proof_is_undecodableCommands run:
cargo test -p dash-sdk --lib address_sync: 45 passedcargo test -p dash-sdk --test main address_sync: passedcargo clippy -p dash-sdk --all-features --all-targets -- -D warnings: cleancargo fmt --allBreaking Changes
None.
Checklist:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull requestFor repository code-owners and collaborators only
🤖 Generated with Claude Code
https://claude.ai/code/session_019H9THphMnuQyh9oPY13SoE