Skip to content

feat(key-wallet-manager): carry per-account balance diff on WalletEvent - #706

Merged
xdustinface merged 3 commits into
v0.42-devfrom
feat/wallet-event-account-balances
May 1, 2026
Merged

feat(key-wallet-manager): carry per-account balance diff on WalletEvent#706
xdustinface merged 3 commits into
v0.42-devfrom
feat/wallet-event-account-balances

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Apr 30, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds account_balances: BTreeMap<AccountType, WalletCoreBalance> to WalletEvent::TransactionDetected, TransactionInstantLocked, and BlockProcessed — only accounts whose balance actually changed between the pre- and post-event state, so most transactions carry a 1–2 entry map.
  • Derives Hash, PartialOrd, Ord on AccountType and StandardAccountType so they can key a BTreeMap.
  • Adds account_balance_snapshot() / changed_account_balances() default methods on WalletInfoInterface and uses them at every emission site in process_block.rs so the snapshot/diff pattern isn't duplicated.

SyncHeightAdvanced is unchanged (carries no balance today). The dash-spv-ffi FFIWalletEventCallbacks::dispatch destructures the new field as account_balances: _ to keep the C ABI surface unchanged; threading it through to a new FFI callback is a separate follow-up.

Why

Downstream consumers (platform-wallet, iOS SDK) need per-account balance breakdowns for UI display. The data was already computed inside ManagedWalletInfo::update_balance() (each ManagedCoreAccount has its own WalletCoreBalance); it just wasn't surfaced on the event.

Test plan

  • cargo build --workspace --all-targets
  • cargo test -p key-wallet --lib (459 passed)
  • cargo test -p key-wallet-manager (31 + 7 + 5 = 43 passed)
  • cargo test -p dash-spv-ffi --lib (44 passed)
  • cargo clippy -p key-wallet -p key-wallet-manager --all-targets
  • cargo fmt --check
  • Existing event tests in event_tests.rs updated to assert the new map only contains the receiving account and reflects the right balance state.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Wallet events now include per-account balance diffs for transaction detection, instant-lock, and block processing, improving visibility into which accounts changed.
    • FFI callbacks and CLI logs now surface the count of changed accounts alongside overall balances.
  • Tests

    • End-to-end tests updated to assert per-account balance diffs are emitted and tracked.

Downstream consumers (platform-wallet, iOS SDK) need per-account balance
breakdowns for UI display. Each `ManagedCoreAccount` already carries its
own `WalletCoreBalance` field that `ManagedWalletInfo::update_balance()`
refreshes; the data was just never surfaced on the event.

Adds an `account_balances: BTreeMap<AccountType, WalletCoreBalance>`
field to `WalletEvent::TransactionDetected`,
`WalletEvent::TransactionInstantLocked`, and
`WalletEvent::BlockProcessed`. The map only contains accounts whose
balance actually changed between the pre- and post-event state, keeping
the payload small for the typical 1–2-account transaction.

`SyncHeightAdvanced` is unchanged (no balance today).

To make `AccountType` usable as a `BTreeMap` key, derives `Hash`,
`PartialOrd`, and `Ord` on `AccountType` and `StandardAccountType`.
Adds `account_balance_snapshot()` and `changed_account_balances()`
default methods on `WalletInfoInterface` so the snapshot/diff pattern is
not duplicated at every emission site.

The dash-spv-ffi `FFIWalletEventCallbacks::dispatch` ignores the new
field for now (`account_balances: _`); surfacing it through the C ABI is
a follow-up.

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

coderabbitai Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds per-account balance snapshots and diffs to wallet events; event production and descriptions were updated to include changed-account maps, types now support ordering/hashing and Display, a default trait method exposes per-account snapshots, and the FFI and tests were extended to carry and verify account-balance diffs.

Changes

Cohort / File(s) Summary
Wallet Event Types
key-wallet-manager/src/events.rs
Added account_balances: BTreeMap<AccountType, WalletCoreBalance> to TransactionDetected, TransactionInstantLocked, and BlockProcessed; added diff_account_balances and format_account_balances; updated WalletEvent::description() to render changed-account data.
Event Emission / Processing
key-wallet-manager/src/process_block.rs
Capture per-wallet per-account balance snapshots, compute diffs via diff_account_balances after mempool/instant-send/block updates, and attach resulting account_balances diffs to emitted WalletEvent variants.
Event Tests
key-wallet-manager/src/event_tests.rs
Extended tests to assert emitted events include account_balances with exactly one AccountType::Standard entry (index 0) and correct confirmed/unconfirmed transitions across detection, instant-lock, insertion, and confirmation scenarios.
Account Type Traits & Display
key-wallet/src/account/account_type.rs
Derived PartialOrd, Ord, and Hash for StandardAccountType and AccountType; added Display impls for both to produce compact, human-readable representations.
Wallet Info Interface
key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs
Added default method account_balances(&self) -> BTreeMap<AccountType, WalletCoreBalance> that snapshots managed accounts into a per-account balance map.
FFI: Callbacks & CLI
dash-spv-ffi/src/callbacks.rs, dash-spv-ffi/src/bin/ffi_cli.rs
Added FFIAccountBalance type; extended FFI callback signatures (OnTransactionDetectedCallback, OnTransactionInstantLockedCallback, OnWalletBlockProcessedCallback) to accept account_balances: *const FFIAccountBalance and account_balances_count: u32; dispatch now converts and forwards temporary array pointer/count and drops it after callback. CLI logging updated to include changed_accounts count.
FFI Tests / Callback Tracker
dash-spv-ffi/tests/dashd_sync/callbacks.rs
Extended CallbackTracker with last_changed_account_count and max_changed_account_count; added record_account_balances to read and log FFIAccountBalance arrays; updated extern "C" callbacks to accept and record account-balance parameters.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through trees of code and found,

balances per-account, neatly bound.
Diffs that whisper which account did grow,
I nibble logs and watch events flow.
Hop, hop — the ledger hums, all sound.

🚥 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: introducing per-account balance diffs to wallet events, which aligns with the substantial changes across multiple files adding account_balances fields and related infrastructure.
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 feat/wallet-event-account-balances

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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 (1)
key-wallet-manager/src/process_block.rs (1)

262-264: ⚡ Quick win

Avoid double balance recomputation in InstantSend lock flow.

mark_instant_send_utxos already refreshes balances when it returns true; the extra info.update_balance() at Line 263 recomputes again with no functional gain.

Proposed simplification
         let mut affected_wallets = Vec::new();
         for (wallet_id, info) in self.wallet_infos.iter_mut() {
             if info.mark_instant_send_utxos(&txid, &instant_lock) {
-                info.update_balance();
                 affected_wallets.push(*wallet_id);
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@key-wallet-manager/src/process_block.rs` around lines 262 - 264, The code
double-recomputes balances: mark_instant_send_utxos(&txid, &instant_lock)
already updates the wallet balance when it returns true, so remove the redundant
call to info.update_balance() in the branch that follows; keep pushing
*wallet_id into affected_wallets but delete the extra info.update_balance()
invocation to avoid the duplicate recomputation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@key-wallet-manager/src/process_block.rs`:
- Around line 262-264: The code double-recomputes balances:
mark_instant_send_utxos(&txid, &instant_lock) already updates the wallet balance
when it returns true, so remove the redundant call to info.update_balance() in
the branch that follows; keep pushing *wallet_id into affected_wallets but
delete the extra info.update_balance() invocation to avoid the duplicate
recomputation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0b046406-3218-414b-ac1f-a314a4b676a6

📥 Commits

Reviewing files that changed from the base of the PR and between e079930 and 31a7e16.

📒 Files selected for processing (6)
  • dash-spv-ffi/src/callbacks.rs
  • key-wallet-manager/src/event_tests.rs
  • key-wallet-manager/src/events.rs
  • key-wallet-manager/src/process_block.rs
  • key-wallet/src/account/account_type.rs
  • key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs

coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 30, 2026
@codecov

codecov Bot commented Apr 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 47.20000% with 66 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.56%. Comparing base (e079930) to head (42aee68).
⚠️ Report is 1 commits behind head on v0.42-dev.

Files with missing lines Patch % Lines
key-wallet/src/account/account_type.rs 0.00% 31 Missing ⚠️
key-wallet-manager/src/events.rs 40.62% 19 Missing ⚠️
dash-spv-ffi/src/callbacks.rs 68.75% 10 Missing ⚠️
dash-spv-ffi/src/bin/ffi_cli.rs 0.00% 6 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           v0.42-dev     #706      +/-   ##
=============================================
- Coverage      70.59%   70.56%   -0.04%     
=============================================
  Files            320      320              
  Lines          68028    68150     +122     
=============================================
+ Hits           48026    48090      +64     
- Misses         20002    20060      +58     
Flag Coverage Δ
core 75.81% <ø> (ø)
ffi 43.37% <57.89%> (+0.04%) ⬆️
rpc 20.00% <ø> (ø)
spv 87.35% <ø> (-0.02%) ⬇️
wallet 69.48% <42.52%> (-0.07%) ⬇️
Files with missing lines Coverage Δ
key-wallet-manager/src/process_block.rs 92.24% <100.00%> (+0.36%) ⬆️
...allet/managed_wallet_info/wallet_info_interface.rs 81.98% <100.00%> (+2.76%) ⬆️
dash-spv-ffi/src/bin/ffi_cli.rs 0.00% <0.00%> (ø)
dash-spv-ffi/src/callbacks.rs 78.42% <68.75%> (-1.01%) ⬇️
key-wallet-manager/src/events.rs 22.03% <40.62%> (+22.03%) ⬆️
key-wallet/src/account/account_type.rs 44.39% <0.00%> (-7.91%) ⬇️

... and 8 files with indirect coverage changes

@github-actions github-actions Bot added the ready-for-review CodeRabbit has approved this PR label Apr 30, 2026
…callbacks

Adds a new `FFIAccountBalance` C-compatible struct (account_type + balance)
and threads it through `OnTransactionDetectedCallback`,
`OnTransactionInstantLockedCallback`, and `OnWalletBlockProcessedCallback`
as a `(*const FFIAccountBalance, u32)` pair. Each entry is the account's
**post-event balance snapshot**, not a delta — only accounts whose balance
actually changed are included, so most transactions deliver 1–2 entries.

Also makes `WalletEvent::description()` print the per-account totals
(e.g. `[Standard{idx:0,BIP44}=>1.5 DASH]`) instead of just the count, so
the log output is actually useful when debugging which account moved.
Adds a compact `format_account_type` label helper that elides Dashpay's
32-byte identity hashes.

Updates the in-tree callback consumers (`ffi_cli`, dashd_sync test
tracker) to the new signatures and tracks both the most-recent and the
running max changed-account count so tests can assert on them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot removed the ready-for-review CodeRabbit has approved this PR label Apr 30, 2026

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

Makes sense and looks mostly good! Left few comments that should be looked at/addressed.

Comment thread key-wallet-manager/src/events.rs Outdated

/// Compact label for an [`AccountType`] suitable for log lines. Avoids
/// printing 32-byte identity hashes from the Dashpay variants.
fn format_account_type(account_type: &AccountType) -> String {

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.

Maybe make this a Display implementation for AccountType?

Comment thread key-wallet-manager/src/process_block.rs Outdated
> = BTreeMap::new();
for wallet_id in &check_result.affected_wallets {
if let Some(info) = self.wallet_infos.get(wallet_id) {
prior_account_balances.insert(*wallet_id, info.account_balance_snapshot());

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.

I think we could get away without the two extra loops by just changing the existing loop below to

for wallet_id in &check_result.affected_wallets {
    if let Some(info) = self.wallet_infos.get_mut(wallet_id) {
        let prior = info.account_balance_snapshot():
        info.update_balance();
        info.changed_account_balances(&prior);
    }
}

/// Reads each account's cached `.balance` field as-is; combine with
/// `update_balance()` (or `update_last_processed_height()`) to control
/// whether the snapshot reflects pre- or post-recompute state.
fn account_balance_snapshot(&self) -> BTreeMap<AccountType, WalletCoreBalance> {

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.

I think this makes sense as part of the interface but it should maybe be generalized? Rename to make it just a normal account balances getter and dont mention all the weird things in the comment.

Suggested change
fn account_balance_snapshot(&self) -> BTreeMap<AccountType, WalletCoreBalance> {
fn account_balances(&self) -> BTreeMap<AccountType, WalletCoreBalance> {

/// the accounts whose balance changed (including accounts that didn't
/// exist in `prior`). Intended to be paired with
/// `account_balance_snapshot()` taken before mutation.
fn changed_account_balances(

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.

I think doesn't make much sense as part of the interface, does it? This should imo be a helper somewhere else? Maybe the events file where its used or some other place where we have helpers.

You can just get account balances before and after the operation and then build the diff from this, no need for another interface function for it.

… surfacing

- Move the per-account label rendering to `impl Display for AccountType`
  in key-wallet (was a free helper in events.rs). `StandardAccountType`
  also gets a `Display` impl. `events.rs::format_account_balances` now
  just uses `{}` formatting.
- Rename `WalletInfoInterface::account_balance_snapshot` to
  `account_balances` and trim the doc to a plain getter.
- Drop `WalletInfoInterface::changed_account_balances` from the trait.
  The diff is a free `pub(crate)` helper `events::diff_account_balances`
  that takes two snapshots — one before, one after — so it doesn't need
  to live on the wallet interface.
- Collapse the three sequential loops in the mempool emission path into
  a single loop that snapshots, refreshes, and diffs in one pass.
- Update the InstantSend lock and `finalize_block_advance` call sites to
  the new helper.

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

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dash-spv-ffi/src/bin/ffi_cli.rs (1)

204-213: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a lint allowance for the expanded extern callback signature.

on_transaction_instant_locked now has enough parameters to trigger clippy::too_many_arguments in stricter setups. Align it with the nearby callback that already has an explicit allow to avoid warning churn.

Suggested patch
+#[allow(clippy::too_many_arguments)]
 extern "C" fn on_transaction_instant_locked(
     wallet_id: *const c_char,
     txid: *const [u8; 32],
     _islock_data: *const u8,

As per coding guidelines, "Run clippy with all features and no warnings allowed."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dash-spv-ffi/src/bin/ffi_cli.rs` around lines 204 - 213, The extern callback
on_transaction_instant_locked has many parameters and will trigger
clippy::too_many_arguments; add the same lint allowance used on the nearby
callback by annotating the function with #[allow(clippy::too_many_arguments)]
(applied to the extern "C" fn on_transaction_instant_locked) so the expanded
signature won't produce warnings in strict lint setups.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@dash-spv-ffi/src/bin/ffi_cli.rs`:
- Around line 204-213: The extern callback on_transaction_instant_locked has
many parameters and will trigger clippy::too_many_arguments; add the same lint
allowance used on the nearby callback by annotating the function with
#[allow(clippy::too_many_arguments)] (applied to the extern "C" fn
on_transaction_instant_locked) so the expanded signature won't produce warnings
in strict lint setups.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 35ff7ccd-cc4b-4bee-a4a6-4c7ab3f97f61

📥 Commits

Reviewing files that changed from the base of the PR and between 31a7e16 and 42aee68.

📒 Files selected for processing (7)
  • dash-spv-ffi/src/bin/ffi_cli.rs
  • dash-spv-ffi/src/callbacks.rs
  • dash-spv-ffi/tests/dashd_sync/callbacks.rs
  • key-wallet-manager/src/events.rs
  • key-wallet-manager/src/process_block.rs
  • key-wallet/src/account/account_type.rs
  • key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • key-wallet/src/account/account_type.rs
  • key-wallet-manager/src/process_block.rs
  • key-wallet-manager/src/events.rs

@github-actions github-actions Bot added the ready-for-review CodeRabbit has approved this PR label May 1, 2026
@xdustinface
xdustinface merged commit 8a68324 into v0.42-dev May 1, 2026
41 of 42 checks passed
@xdustinface
xdustinface deleted the feat/wallet-event-account-balances branch May 1, 2026 08:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review CodeRabbit has approved this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants