feat(key-wallet-manager): carry per-account balance diff on WalletEvent - #706
Conversation
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>
📝 WalkthroughWalkthroughAdds 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
key-wallet-manager/src/process_block.rs (1)
262-264: ⚡ Quick winAvoid double balance recomputation in InstantSend lock flow.
mark_instant_send_utxosalready refreshes balances when it returnstrue; the extrainfo.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
📒 Files selected for processing (6)
dash-spv-ffi/src/callbacks.rskey-wallet-manager/src/event_tests.rskey-wallet-manager/src/events.rskey-wallet-manager/src/process_block.rskey-wallet/src/account/account_type.rskey-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs
Codecov Report❌ Patch coverage is 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
|
…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>
xdustinface
left a comment
There was a problem hiding this comment.
Makes sense and looks mostly good! Left few comments that should be looked at/addressed.
|
|
||
| /// 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 { |
There was a problem hiding this comment.
Maybe make this a Display implementation for AccountType?
| > = 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()); |
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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.
| 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( |
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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 winAdd a lint allowance for the expanded extern callback signature.
on_transaction_instant_lockednow has enough parameters to triggerclippy::too_many_argumentsin 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
📒 Files selected for processing (7)
dash-spv-ffi/src/bin/ffi_cli.rsdash-spv-ffi/src/callbacks.rsdash-spv-ffi/tests/dashd_sync/callbacks.rskey-wallet-manager/src/events.rskey-wallet-manager/src/process_block.rskey-wallet/src/account/account_type.rskey-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
Summary
account_balances: BTreeMap<AccountType, WalletCoreBalance>toWalletEvent::TransactionDetected,TransactionInstantLocked, andBlockProcessed— only accounts whose balance actually changed between the pre- and post-event state, so most transactions carry a 1–2 entry map.Hash,PartialOrd,OrdonAccountTypeandStandardAccountTypeso they can key aBTreeMap.account_balance_snapshot()/changed_account_balances()default methods onWalletInfoInterfaceand uses them at every emission site inprocess_block.rsso the snapshot/diff pattern isn't duplicated.SyncHeightAdvancedis unchanged (carries no balance today). Thedash-spv-ffiFFIWalletEventCallbacks::dispatchdestructures the new field asaccount_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()(eachManagedCoreAccounthas its ownWalletCoreBalance); it just wasn't surfaced on the event.Test plan
cargo build --workspace --all-targetscargo 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-targetscargo fmt --checkevent_tests.rsupdated 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
Tests