fix(drive-abci): record unshield and shield-surplus credits in recent address balance changes#4142
fix(drive-abci): record unshield and shield-surplus credits in recent address balance changes#4142QuantumExplorer wants to merge 4 commits into
Conversation
📝 WalkthroughWalkthroughChangesTransparent credit output propagation
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant StateTransition
participant ExecutionEvent
participant execute_event_v0
participant PaidConsensusError
participant ProcessingResult
StateTransition->>ExecutionEvent: construct optional credit outputs
ExecutionEvent->>execute_event_v0: execute paid event
execute_event_v0->>PaidConsensusError: return address balance changes
PaidConsensusError->>ProcessingResult: aggregate balance changes
Possibly related PRs
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 |
|
🔍 Review in progress — actively reviewing now (commit 02e9f01) |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## v4.1-dev #4142 +/- ##
============================================
+ Coverage 87.43% 87.45% +0.01%
============================================
Files 2646 2646
Lines 333981 334461 +480
============================================
+ Hits 292023 292488 +465
- Misses 41958 41973 +15
🚀 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)
… 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>
a80ce1d to
02e9f01
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>
02e9f01 to
50ce3bb
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 what gets written under
RootTree::SavedBlockTransactionsper block — a state-root change — so the recording method itself is versioned, per theplatform_eventsidiom:record_added_balance_outputs/{mod.rs, v0/, v1/}is its own versionedPlatformmethod, dispatched on a newrecord_added_balance_outputs: FeatureVersionfield (0in V1..V8,1in the newDRIVE_ABCI_METHOD_VERSIONS_V9, whichPLATFORM_V13adopts as its only delta from v12).AddedBalanceOutputsOrigindiscriminator: v0 foldsTransparent-origin outputs (thePaidarm's long-standing behavior) and dropsShieldedSpend-origin outputs — so pre-v13 blocks byte-match old behavior; v1 folds both.PaidConsensusErrorcarry stay unconditional too.store_address_balances_to_recent_block_storageis untouched atSome(0)— its_v0implementation did not change, so its version does not move; its dispatch file is byte-identical to base.get(12)/get(13)dispatch), construction'''s version-independence at both versions, and the version-struct delta itself (field0at v12,1at v13; store methodSome(0)at both).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