fix(drive-abci): record unshield and shield-surplus credits in recent address balance changes#4142
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughExecution events now carry optional transparent credit outputs for unshielding and asset-lock shielding. Versioned recording folds these credits into address balances, propagates them through paid consensus errors, and aggregates them into block-level balance updates. ChangesTransparent credit output propagation
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant StateTransitionAction
participant ExecutionEvent
participant execute_event_v0
participant PaidConsensusError
participant StateTransitionsProcessingResult
StateTransitionAction->>ExecutionEvent: construct optional credit outputs
ExecutionEvent->>execute_event_v0: execute paid event
execute_event_v0->>PaidConsensusError: attach address balance changes
PaidConsensusError->>StateTransitionsProcessingResult: aggregate address balance changes
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
🕓 Ready for review — 35 ahead in queue (commit cbd5b86) |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## v4.1-dev #4142 +/- ##
============================================
+ Coverage 87.44% 87.46% +0.01%
============================================
Files 2648 2658 +10
Lines 334178 335741 +1563
============================================
+ Hits 292230 293663 +1433
- Misses 41948 42078 +130
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/execute_event/v0/mod.rs (1)
596-621: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the repeated address-credit merge into a helper.
These blocks duplicate each other and the
Paidarm’s Set/Add semantics. Centralizing this logic will prevent event paths from silently diverging when merge behavior changes.Also applies to: 674-699
🤖 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 `@packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/execute_event/v0/mod.rs` around lines 596 - 621, Extract the address-credit merge logic from the shown block and the corresponding block around the Paid arm into a shared helper, using the existing balance-update map and address/credits inputs. Preserve the current SetCredits + AddToCredits and AddToCredits + AddToCredits saturating-add semantics, then replace both duplicated loops with calls to the helper.
🤖 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/types/execution_event/mod.rs`:
- Around line 95-102: Update the documentation for added_to_balance_outputs to
include the identity-create duplicate-key fallback surfaced as an
UnshieldAction, which may credit a transparent address. Keep the existing
Unshield net-positive amount behavior accurate while revising the statement that
all IdentityCreateFromShieldedPool paths produce None.
---
Nitpick comments:
In
`@packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/execute_event/v0/mod.rs`:
- Around line 596-621: Extract the address-credit merge logic from the shown
block and the corresponding block around the Paid arm into a shared helper,
using the existing balance-update map and address/credits inputs. Preserve the
current SetCredits + AddToCredits and AddToCredits + AddToCredits saturating-add
semantics, then replace both duplicated loops with calls to the helper.
🪄 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: 632c9b40-1bdf-47cc-a529-2253dcace728
📒 Files selected for processing (3)
packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/execute_event/v0/mod.rspackages/rs-drive-abci/src/execution/platform_events/state_transition_processing/validate_fees_of_event/v0/mod.rspackages/rs-drive-abci/src/execution/types/execution_event/mod.rs
thepastaclaw
left a comment
There was a problem hiding this comment.
Preliminary review — Codex only
Fully revalidated the cumulative PR range b889e05..87dab29 and latest-push delta 5c335bc..87dab29. Normal Unshield and ShieldFromAssetLock-surplus credits are recorded, but the IdentityCreateFromShieldedPool duplicate-key fallback is an applied, chargeable invalid transition whose caller passes no balance-change map; PaidConsensusError cannot carry the discarded credit and aggregation only consumes SuccessfulExecution payloads. The claimed fallback regression calls execute_event with None and asserts only the paid result and pool debit, while the new map test supplies Some(&mut map) directly, so neither test covers the failing process_raw_state_transitions-to-recent-storage path; local focused test attempts were blocked by the Tenderdash source download requiring unavailable network access.
Validated blockers were found in the Codex precheck. Sonnet is deferred until a fresh Codex revalidation clears the blocker gate.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (failed),gpt-5.6-sol— rust-quality (failed),gpt-5.6-sol— general (failed),gpt-5.6-sol— rust-quality (failed),gpt-5.6-sol— general (failed),gpt-5.6-sol— general (failed),gpt-5.6-sol— general (completed),gpt-5.6-sol— rust-quality (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet: not run (deferred by blocker gate)
🔴 1 blocking
1 additional finding(s) omitted (not in diff).
🤖 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/state_transition_processing/process_raw_state_transitions/v0/mod.rs`:
- [BLOCKING] packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/process_raw_state_transitions/v0/mod.rs:278-284: Propagate fallback address credits from paid-invalid transitions
The duplicate-key IdentityCreateFromShieldedPool fallback returns an Unshield action with consensus errors and chargeable_failure=true. execute_event applies that event's operations, including the transparent fallback-address credit, and calls record_added_balance_outputs, but this invalid-transition branch passes None for address_balances_in_update, so the credit is discarded. The resulting PaidConsensusError contains only the error and fees, and StateTransitionsProcessingResult::add merges address changes only from SuccessfulExecution; no later path reconstructs the applied credit before address_balances_updated is stored as the block's recent address-balance changes. Incremental sync therefore omits a GroveDB balance credit that the block actually applied. Preserve the balance-change map for applied paid-invalid events, carry it through PaidConsensusError, aggregate it into address_balances_updated, and add a regression that processes the duplicate-key fallback through process_raw_state_transitions and asserts the recent-balance accumulator or stored entry.
|
Blocker addressed in the latest push:
🤖 Addressed by Claude Code |
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Codex + Sonnet
At exact head 23675f4, no in-scope defects remain. The final commit fixes the sole prior blocker by threading a mutable address-balance map through the invalid branch of process_raw_state_transitions, carrying it via a new address_balance_changes field on PaidConsensusError, and merging it into address_balances_updated exactly like SuccessfulExecution. I independently re-read the diff and reran both regression tests (invalid_branch_chargeable_failure_carries_address_credit and add_merges_paid_consensus_error_address_changes) against the working tree at this SHA — both pass, corroborating the Codex and Sonnet lanes' independent findings.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (failed),gpt-5.6-sol— security-auditor (failed),gpt-5.6-sol— rust-quality (failed),gpt-5.6-sol— general (failed),gpt-5.6-sol— security-auditor (failed),gpt-5.6-sol— rust-quality (failed),gpt-5.6-sol— general (failed),gpt-5.6-sol— security-auditor (failed),gpt-5.6-sol— rust-quality (failed),gpt-5.6-sol— general (completed),gpt-5.6-sol— security-auditor (completed),gpt-5.6-sol— rust-quality (completed) - Verifier:
claude-sonnet-5— final-verifier - Sonnet reviewers:
claude-sonnet-5— general (failed),claude-sonnet-5— security-auditor (failed),claude-sonnet-5— rust-quality (failed),claude-sonnet-5— general (completed),claude-sonnet-5— security-auditor (completed),claude-sonnet-5— rust-quality (completed)
a80ce1d to
02e9f01
Compare
02e9f01 to
50ce3bb
Compare
… address balance changes A Type 17 Unshield credits its transparent platform output address via a raw AddBalanceToAddress drive op, but the PaidFromShieldedPool execution arm never recorded the credit into address_balances_in_update — the sole feed for store_address_balances_for_block, i.e. the recent address-balance-changes tree that incremental client sync (GetRecentAddressBalanceChanges) reads. The absolute address-funds tree was credited, so full trunk/branch scans saw the payment while every incremental pass returned 0 entries: wallets showed unshield-received platform payments only after a full rescan (observed on iOS: 18 recent queries, 0 entries, last_known_recent_block stuck at 0 while the balance appeared after relaunch). PaidFromAssetLockToPool had the identical omission for a Type 18 ShieldFromAssetLock surplus output. Thread the credited outputs through both execution-event variants (added_to_balance_outputs, same shape as Paid's) and fold them into address_balances_in_update on the applied path with the same entry/merge idiom as the Paid arm. Type 16 ShieldedTransfer and Type 19 ShieldedWithdrawal pass None — neither credits a transparent platform address. Tests: construction coverage (net output, net-zero, surplus, no-surplus) plus executor-level folds proving the map receives AddToCredits for both variants. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e identity-create fallback credit - cargo fmt (the macOS CI formatting failure) - Extract the triplicated Set/Add address-credit merge (Paid / PaidFromShieldedPool / PaidFromAssetLockToPool arms) into one record_added_balance_outputs helper so the merge semantics can't silently diverge between event paths (CodeRabbit) - Correct the added_to_balance_outputs doc: the IdentityCreateFromShieldedPool duplicate-key fallback surfaces as an UnshieldAction and DOES credit the fallback address (CodeRabbit) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ess_balances_updated Review blocker: the IdentityCreateFromShieldedPool duplicate-key fallback is an APPLIED, chargeable, paid-INVALID transition (a chargeable_failure UnshieldAction) — but the invalid branch of process_raw_state_transitions passed None for address-balance tracking, PaidConsensusError had no field to carry the credit, and StateTransitionsProcessingResult::add merged address changes only from SuccessfulExecution. The fallback-address credit the block actually applied never reached the recent-address-balance-changes tree, so incremental sync omitted it. - Track address balances through the invalid branch (the executor only records on the applied path, so bump-only paid-invalid transitions leave the map empty). - PaidConsensusError gains address_balance_changes (same shape as SuccessfulExecution's) — populated at both construction sites, including the valid-branch UnsuccessfulPaidExecution arm, which had the identical latent omission — and add() merges it into address_balances_updated. - Tests pin the chain at each seam: the real fallback UnshieldAction produces the credited event (construction), the invalid branch carries the credit out (process_validation_result seam), and add() merges PaidConsensusError maps into address_balances_updated. A full process_raw fixture would need a real Halo 2 proof (the fallback arises only after proof verification succeeds), which no existing fixture produces. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
50ce3bb to
0e53820
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
9d95a18 to
3075173
Compare
…protocol v13 Recording these credits adds writes to the recent-address-balance- changes tree, changing the committed state root — mixed-version validators would fork on the first affected transition. The gate is the recording method itself, versioned per the platform_events idiom: record_added_balance_outputs is its own method module with a real v0 and v1, dispatched on a new DriveAbciStateTransitionProcessingMethodVersions:: record_added_balance_outputs FeatureVersion (0 in V1..V8, 1 in the new DRIVE_ABCI_METHOD_VERSIONS_V9, which PLATFORM_V13 adopts as its only delta from v12). Call sites pass an origin discriminator: v0 folds Transparent-origin outputs (the Paid arm's long-standing behavior) and DROPS ShieldedSpend-origin outputs — byte-matching pre-v13 blocks; v1 folds both. Event construction populates added_to_balance_outputs unconditionally (the event is in-memory only; v0 recording discards shielded-origin credits), and every other downstream stage (paid-invalid tracking, PaidConsensusError carry) stays unconditional. store_address_balances_to_recent_block_storage is untouched at Some(0) — its implementation did not change. Tests pin the boundary at the recording layer (v0 drops shielded / keeps transparent; v1 records both), construction's version-independence at v12 and v13, and the version-struct delta itself (field 0 at v12, 1 at v13, store method unchanged at both). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3075173 to
cbd5b86
Compare
Symptom (dashwallet-ios QA)
Platform payments received via Unshield (Type 17) never appear through incremental address sync — the wallet's
GetRecentAddressBalanceChangespasses returned 0 entries across 18 consecutive queries withlast_known_recent_blockstuck at 0, while the same payments showed up immediately after a full trunk/branch rescan (app relaunch / sync-state clear). Receivers see the money "sometimes" — i.e. only after the next full scan.Root cause
UnshieldActioncredits the transparent platform output address by pushing a rawAddressFundsOperationType::AddBalanceToAddressdrive op (action_convert_to_operations/shielded/unshield_transition.rs), which mutates the absolute address-funds tree — what full scans read. But its execution eventPaidFromShieldedPoolcarried no output data, and the executor arm never wrote the credit intoaddress_balances_in_update— the sole feed forstore_address_balances_for_block, i.e. the recent-address-balance-changes tree (SavedBlockTransactions/'m') that incremental sync queries. The recent tree was genuinely empty for unshield-credited blocks.PaidFromAssetLockToPoolhad the identical omission for a Type 18 ShieldFromAssetLocksurplus_outputcredit.All transparent-side transitions (
AddressFundsTransfer,IdentityCreditTransferToAddresses, fundings, withdrawals, identity ops) already record correctly viaPaidFromAddressInputs/Paid { added_to_balance_outputs }. The client-side apply in rs-sdk was verified correct — it would have applied entries had any existed.Fix
ExecutionEvent::PaidFromShieldedPooland::PaidFromAssetLockToPoolwithadded_to_balance_outputs: Option<BTreeMap<PlatformAddress, Credits>>(same shape asPaid's field).output_address → amount − fee(only when net > 0, mirroring the converter's guard).None— neither credits a transparent platform address (Type 19's output is a Core-chain address).address_balances_in_updateasCreditOperation::AddToCreditson the applied path, using the identical entry/merge idiom as thePaidarm (including the chargeable-failure fallback, whose address is genuinely credited by the applied ops).Rolling-upgrade safety (protocol v13)
The recording changes the committed state root, so each changed behavior — and ONLY the changed behavior — is a versioned method with a real v0/v1:
record_added_balance_outputs/{mod.rs, v0/, v1/}(new method, field 0 in V1..V8 → 1 in V9): v0 foldsTransparent-origin outputs, dropsShieldedSpend-origin; v1 folds both. Events carry credits unconditionally (in-memory only).process_validation_result/{mod.rs, v0/, v1/}(the per-ST result helper, hoisted into its own method with a new field, 0 in V1..V8 → 1 in V9): v0 = base behavior (paid-invalid passesNone,PaidConsensusErrorcarries empty); v1 = unconditional paid-invalid tracking + carry intoaddress_balances_updated. The outerprocess_raw_state_transitionsloop did not change and stays at v0 — it just calls the dispatching helper, same asexecute_event_v0calls the dispatchingrecord_added_balance_outputs.DRIVE_ABCI_METHOD_VERSIONS_V9therefore carries exactly two field deltas — together they define the v13 recorded-set expansion: shielded-spend transparent credits (Unshield net output, shield surplus, identity-create fallback) plus the previously-dropped balance effects of paid-invalid / unsuccessful-paid transitions (charged fees, adjusted outputs).store_address_balances_to_recent_block_storageuntouched atSome(0); theprocess_raw_state_transitionsdispatch, store dispatch, andfeature_initial_protocol_versionsare byte-identical to base.Tests
address_balances_in_updatereceivesAddToCreditsfor both variants and stays empty without outputscargo check -p drive-abci/-p driveclean; touched-module suites green (17 passed drive-abci filter, 43 passed drive saved_block_transactions/converters)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests