Skip to content

chore: merge v0.42-dev into feat/per-wallet-filter-scan#698

Merged
QuantumExplorer merged 3 commits intofeat/per-wallet-filter-scanfrom
merge/v0.42-dev-into-per-wallet-filter-scan
Apr 28, 2026
Merged

chore: merge v0.42-dev into feat/per-wallet-filter-scan#698
QuantumExplorer merged 3 commits intofeat/per-wallet-filter-scanfrom
merge/v0.42-dev-into-per-wallet-filter-scan

Conversation

@QuantumExplorer
Copy link
Copy Markdown
Member

Resolves the merge conflicts blocking #694 by merging the latest v0.42-dev into feat/per-wallet-filter-scan. Once this lands on the feature branch, #694 will go from CONFLICTING to mergeable.

Source of conflicts

v0.42-dev advanced ahead of the feature branch by #695 (masternode seed refresh, no overlap) and #696 (atomic wallet events). The wallet-events refactor changed the same files that #694 restructured around per-wallet processing:

File Conflict
key-wallet-manager/src/lib.rs feat branch emits events from check_transaction_in_wallets; #696 moved emission to callers so events can carry the post-balance
key-wallet-manager/src/process_block.rs feat branch added per-wallet API (process_block_for_wallets, wallets_behind, update_wallet_synced_height, …); #696 added finalize_block_advance and replaced BalanceUpdated/TransactionReceived/TransactionStatusChanged with TransactionDetected/TransactionInstantLocked/BlockProcessed/SyncHeightAdvanced
key-wallet-manager/src/event_tests.rs #696 rewrote almost the entire file against the new event shapes, while the feat branch had updated several tests for the per-wallet API

Resolution

  • lib.rs — drop the in-place event emission inside check_transaction_in_wallets (per feat: make wallet events atomic #696, callers emit after refreshing balance). Keep feat: per-wallet filter scan and runtime wallet catch-up #694's new_addresses BTreeMap<WalletId, _> so per-wallet attribution survives.
  • process_block.rs
    • process_block_for_wallets now collects per_wallet_inserted/per_wallet_updated per feat: make wallet events atomic #696 and calls finalize_block_advance(height, wallets, …) to emit BlockProcessed only for the listed wallets.
    • finalize_block_advance is parameterized on the wallet set, so the per-wallet API only advances/emits for those wallets. Wallets already at or past height skip the height bump but still refresh balance — preserving the rescan-without-going-backwards behavior introduced on the feat branch.
    • update_wallet_synced_height now emits SyncHeightAdvanced for the wallet that advanced (the per-wallet analog of feat: make wallet events atomic #696's update_synced_height).
    • update_wallet_last_processed_height delegates to finalize_block_advance over a single-wallet set, so it emits BlockProcessed when the balance moved (matching what feat: make wallet events atomic #696's deleted update_last_processed_height did, but for one wallet at a time).
    • Renamed test_process_block_emits_balance_updatedtest_process_block_emits_block_processed; renamed/rewrote test_update_synced_height_*test_update_wallet_synced_height_* against the per-wallet API.
  • event_tests.rs — start from feat: make wallet events atomic #696's rewritten file, then adapt API calls:
    • manager.process_block(&block, h)manager.process_block_for_wallets(&block, h, &BTreeSet::from([wallet_id]))
    • manager.update_synced_height(h)manager.update_wallet_synced_height(&wallet_id, h)

Verification

  • cargo build --workspace
  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace --exclude dash-spv --exclude dash-spv-ffi --exclude integration_test
  • cargo test -p key-wallet-manager (30/30 unit tests pass, including all event tests)
  • cargo test -p dash-spv --lib (404/404 pass)

Test plan

  • CI green on the merge branch
  • After merge, #694 shows MERGEABLE and CI is green there too
  • Spot-check a WalletEvent::BlockProcessed event in an SPV integration test (e.g. dashd_sync) — it should carry per-wallet inserted/updated/matured/balance for the right wallet and only fire for wallets that were behind

🤖 Generated with Claude Code

github-actions Bot and others added 3 commits April 28, 2026 11:36
Co-authored-by: QuantumExplorer <11468583+QuantumExplorer@users.noreply.github.com>
* feat: make wallet events atomic

Reshape `WalletEvent` so each variant carries the records or context needed to persist a wallet update atomically off a single event, alongside the post-change balance.

The variant set is now:

- `TransactionReceived { wallet_id, record, balance }`. Fires when the wallet first sees an off-chain transaction.
- `TransactionStatusChanged { wallet_id, txid, context, balance }`. Fires when a known off-chain transaction has its state change. Currently fires only for InstantSend locks.
- `BlockUpdate { wallet_id, height, inserted, updated, matured, balance }`. Carries records bucketed by what happened to them in the block, plus the post-block balance.
- `SyncHeightUpdate { wallet_id, height }`. Marks a filter-batch checkpoint.

`TransactionRecord` carries `account_type` directly, identifying the owning account. `WalletInfoInterface` gains a `matured_coinbase_records` method that enumerates coinbase records crossing the maturity threshold during a height advance, populating `BlockUpdate.matured`.

The FFI groups the flattened account-discriminator fields into an `FFIAccountType` struct and renames the prior discriminant enum to `FFIAccountKind`.

* fix: record balance before bumping `block_processed_wallet_count`

Tests wait on `block_processed_wallet_count` and then read `last_confirmed`/`last_unconfirmed`. Bumping the counter before storing the balance snapshot left those reads racey. Reorder so the balance is recorded first.

Addresses CodeRabbit review comment on PR #696
#696 (comment)

* fix: place `IdentityTopUp.registration_index` in `index_secondary`

The `FFIAccountType` doc states `index_secondary` carries `registration_index` for `IdentityTopUp` and `index = 0` for variants without a meaningful primary index, matching the parallel encoding in `FFIAccountKind::from_account_type`. The `From<&AccountType>` impl wrote `registration_index` into `index` instead, breaking the documented FFI contract.

Addresses CodeRabbit review comment on PR #696
#696 (comment)

* fix: route confirmation backfills to `new_records`

`is_new` is wallet-wide (set on the first matching account, then breaks), so the per-account `else` branch can run for an account that did not previously hold the record. `confirm_transaction` backfills via `record_transaction` in that case, but the post-call record was always pushed onto `updated_records`, breaking the atomic `inserted`/`updated` contract consumed by `WalletEvent::BlockUpdate`. Capture `existed_before` per account and route to `new_records` when the record was just created.

Addresses CodeRabbit review comment on PR #696
#696 (comment)

* refactor(key-wallet-manager): extract `finalize_block_advance` helper

`process_block` and `update_last_processed_height` duplicated the entire balance-snapshot, prior-heights collection, matured-coinbase window, height advance, and per-wallet `BlockUpdate` emission. Extract the shared tail into a private `WalletManager::finalize_block_advance` helper that takes the inserted/updated maps. `update_last_processed_height` becomes a one-line call with empty maps; `process_block` keeps only its txdata loop before delegating.

* refactor: rename wallet events for clearer semantics

Rename `WalletEvent` variants and the matching FFI callbacks to past-participle names that say what happened, replacing vague "Update" suffixes:

- `TransactionReceived` -> `TransactionDetected`. "Received" implied incoming funds, but the event fires for any first-time off-chain sighting (incoming or outgoing).
- `TransactionStatusChanged` -> `TransactionInstantLocked`. The event only ever fires for an InstantSend lock applied to a known mempool tx, so name it for what it actually is. Drop the `status: TransactionContext` field and carry the `InstantLock` directly.
- `BlockUpdate` -> `BlockProcessed`. Mirrors `process_block` and matches the past-participle pattern.
- `SyncHeightUpdate` -> `SyncHeightAdvanced`. Conveys monotonic forward motion.

FFI rename mirrors the Rust side: the IS callback now takes `islock_data: *const u8` + `islock_len: usize` instead of an `FFITransactionContext`, removing a discriminant that was always `InstantSend`. The wallet-side `OnBlockProcessedCallback` becomes `OnWalletBlockProcessedCallback` to disambiguate from the existing sync-event type with the same name.

* fix: record balance before bumping IS-locked counter in test callback

Addresses CodeRabbit review comment on PR #696
#696 (review)

The instant_locked callback bumped `transaction_instant_send_locked_count` before calling `record_balance`. Tests that wait on the counter and then read `last_confirmed`/`last_unconfirmed` could observe the previous balance snapshot. Match the ordering used by the other callbacks: store the balance first, then bump the counter.

* fix: backfill missing transaction record in InstantSend path

Addresses CodeRabbit review comment on PR #696
#696 (review)

The IS-lock branch in `WalletTransactionChecker::check_core_transaction` only updated accounts that already held a `TransactionRecord` for the txid. When wallet-level `is_new` was `false` (because at least one account had the record) but another matched account did not, the latter was silently skipped: no record was created and `mark_utxos_instant_send` ran against an empty UTXO set on that account.

Mirror the confirmation path: when the affected account lacks the record, call `record_transaction` to register the record and its UTXOs, then mark them IS-locked. This ordering ensures the freshly registered UTXOs receive the IS-lock flag too. The backfilled record is pushed into `new_records` to match the existing convention from commit 659a6d5.

Add `test_instantsend_backfills_missing_record_in_other_account` covering the multi-account scenario.
Resolves conflicts between the per-wallet filter scan work (#694) and the
atomic wallet events work (#696) from v0.42-dev.

Conflict resolution:

- `key-wallet-manager/src/lib.rs`: drop in-place event emission from
  `check_transaction_in_wallets`. Per #696, callers are responsible for
  emitting events after refreshing balances so events carry the
  post-change balance. The `CheckTransactionsResult.per_wallet_*` maps
  remain the per-wallet attribution channel.

- `key-wallet-manager/src/process_block.rs`:
  - `process_block_for_wallets` (per-wallet API from #694) now collects
    `per_wallet_inserted` / `per_wallet_updated` and finalizes via
    `finalize_block_advance` (atomic events from #696) instead of the
    old `BalanceUpdated` snapshot/emit pair.
  - `finalize_block_advance` is parameterized on the wallet set so
    `process_block_for_wallets` and `update_wallet_last_processed_height`
    only advance and emit `BlockProcessed` for the listed wallets.
    Wallets already at or past `height` skip the height bump but still
    refresh their balance, preserving the rescan-without-going-backwards
    semantics from the feat branch.
  - `update_wallet_synced_height` now emits `SyncHeightAdvanced` for the
    one wallet whose height advanced (replacing the all-wallets
    `update_synced_height` from v0.42-dev).
  - `update_wallet_last_processed_height` delegates to
    `finalize_block_advance` over a single-wallet set so it emits a
    `BlockProcessed` if the balance moved, matching the v0.42-dev
    behavior of the deleted `update_last_processed_height`.
  - Test names updated for the new event types.

- `key-wallet-manager/src/event_tests.rs`: take the rewritten v0.42-dev
  test file (designed against the new atomic events) and adapt the
  legacy-API call sites: `process_block(&block, h)` becomes
  `process_block_for_wallets(&block, h, &BTreeSet::from([wallet_id]))`,
  and `update_synced_height(h)` becomes
  `update_wallet_synced_height(&wallet_id, h)`.

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

coderabbitai Bot commented Apr 28, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 72813451-ce4b-463a-8170-b234afcb7ef8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch merge/v0.42-dev-into-per-wallet-filter-scan

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

❌ Patch coverage is 67.39927% with 178 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.52%. Comparing base (541d0cf) to head (2efd17c).
⚠️ Report is 1 commits behind head on feat/per-wallet-filter-scan.

Files with missing lines Patch % Lines
dash-spv-ffi/src/bin/ffi_cli.rs 0.00% 55 Missing ⚠️
key-wallet-ffi/src/managed_account.rs 48.07% 54 Missing ⚠️
key-wallet-ffi/src/types.rs 36.36% 21 Missing ⚠️
key-wallet-manager/src/events.rs 0.00% 21 Missing ⚠️
dash-spv-ffi/src/callbacks.rs 81.35% 11 Missing ⚠️
key-wallet-ffi/src/account.rs 30.00% 7 Missing ⚠️
key-wallet-ffi/src/address_pool.rs 40.00% 3 Missing ⚠️
key-wallet-ffi/src/wallet.rs 33.33% 2 Missing ⚠️
key-wallet-manager/src/process_block.rs 98.14% 2 Missing ⚠️
...allet/managed_wallet_info/wallet_info_interface.rs 90.90% 2 Missing ⚠️
Additional details and impacted files
@@                       Coverage Diff                       @@
##           feat/per-wallet-filter-scan     #698      +/-   ##
===============================================================
- Coverage                        70.65%   70.52%   -0.13%     
===============================================================
  Files                              320      320              
  Lines                            67692    67992     +300     
===============================================================
+ Hits                             47827    47952     +125     
- Misses                           19865    20040     +175     
Flag Coverage Δ
core 75.81% <ø> (ø)
ffi 43.53% <45.16%> (-1.20%) ⬇️
rpc 20.00% <ø> (ø)
spv 87.38% <ø> (+0.09%) ⬆️
wallet 69.18% <90.63%> (+0.25%) ⬆️
Files with missing lines Coverage Δ
key-wallet-ffi/src/account_collection.rs 55.93% <100.00%> (-0.70%) ⬇️
key-wallet-ffi/src/transaction_checking.rs 1.90% <ø> (ø)
key-wallet-manager/src/accessors.rs 53.65% <100.00%> (-10.38%) ⬇️
key-wallet-manager/src/lib.rs 62.88% <100.00%> (-0.25%) ⬇️
key-wallet-manager/src/test_helpers.rs 100.00% <ø> (ø)
key-wallet/src/managed_account/mod.rs 55.12% <100.00%> (+0.06%) ⬆️
...y-wallet/src/managed_account/transaction_record.rs 100.00% <100.00%> (ø)
...wallet/src/transaction_checking/account_checker.rs 67.02% <100.00%> (+0.51%) ⬆️
...-wallet/src/transaction_checking/wallet_checker.rs 99.34% <100.00%> (+0.05%) ⬆️
key-wallet-ffi/src/wallet.rs 25.22% <33.33%> (+2.72%) ⬆️
... and 9 more

... and 13 files with indirect coverage changes

@QuantumExplorer QuantumExplorer merged commit a8e381a into feat/per-wallet-filter-scan Apr 28, 2026
35 checks passed
@QuantumExplorer QuantumExplorer deleted the merge/v0.42-dev-into-per-wallet-filter-scan branch April 28, 2026 05:20
QuantumExplorer added a commit that referenced this pull request Apr 28, 2026
* feat: per-wallet filter scan and runtime wallet catch-up

Filter matching and block processing now operate per wallet, so a wallet added at runtime catches up without forcing the already-synced wallets to reprocess anything.

- `WalletInterface` restructured around per-wallet ops: `process_block_for_wallets`, `wallets_behind`, `monitored_addresses_for`, `wallet_synced_height`, and monotonic per-wallet height updates. Aggregate heights are derived (min of `synced_height`, max of `last_processed_height`) rather than stored.
- `FiltersManager::scan_batch` matches each behind wallet's addresses only against filter heights it hasn't yet covered; already-synced wallets are skipped entirely. Matched blocks flow through `BlocksNeeded` carrying the per-block wallet set so `BlocksManager` processes each block only against the wallets whose filters matched. `FiltersBatch` records the scanned-wallet set so commit advances only their `synced_height`.
- `FiltersManager::tick` detects when a wallet's `synced_height` sits below the current `committed_height` (a runtime add behind scan progress), clears in-flight pipeline state, lowers `committed_height` to the new aggregate floor, and re-enters `start_download` on the next 100ms tick. Runs in `Syncing`, `Synced`, and `WaitForEvents`.

Based on:
- #689

* chore: merge v0.42-dev into feat/per-wallet-filter-scan (#698)

* chore(dash-spv): refresh masternode seed files (#695)

Co-authored-by: QuantumExplorer <11468583+QuantumExplorer@users.noreply.github.com>

* feat: make wallet events atomic (#696)

* feat: make wallet events atomic

Reshape `WalletEvent` so each variant carries the records or context needed to persist a wallet update atomically off a single event, alongside the post-change balance.

The variant set is now:

- `TransactionReceived { wallet_id, record, balance }`. Fires when the wallet first sees an off-chain transaction.
- `TransactionStatusChanged { wallet_id, txid, context, balance }`. Fires when a known off-chain transaction has its state change. Currently fires only for InstantSend locks.
- `BlockUpdate { wallet_id, height, inserted, updated, matured, balance }`. Carries records bucketed by what happened to them in the block, plus the post-block balance.
- `SyncHeightUpdate { wallet_id, height }`. Marks a filter-batch checkpoint.

`TransactionRecord` carries `account_type` directly, identifying the owning account. `WalletInfoInterface` gains a `matured_coinbase_records` method that enumerates coinbase records crossing the maturity threshold during a height advance, populating `BlockUpdate.matured`.

The FFI groups the flattened account-discriminator fields into an `FFIAccountType` struct and renames the prior discriminant enum to `FFIAccountKind`.

* fix: record balance before bumping `block_processed_wallet_count`

Tests wait on `block_processed_wallet_count` and then read `last_confirmed`/`last_unconfirmed`. Bumping the counter before storing the balance snapshot left those reads racey. Reorder so the balance is recorded first.

Addresses CodeRabbit review comment on PR #696
#696 (comment)

* fix: place `IdentityTopUp.registration_index` in `index_secondary`

The `FFIAccountType` doc states `index_secondary` carries `registration_index` for `IdentityTopUp` and `index = 0` for variants without a meaningful primary index, matching the parallel encoding in `FFIAccountKind::from_account_type`. The `From<&AccountType>` impl wrote `registration_index` into `index` instead, breaking the documented FFI contract.

Addresses CodeRabbit review comment on PR #696
#696 (comment)

* fix: route confirmation backfills to `new_records`

`is_new` is wallet-wide (set on the first matching account, then breaks), so the per-account `else` branch can run for an account that did not previously hold the record. `confirm_transaction` backfills via `record_transaction` in that case, but the post-call record was always pushed onto `updated_records`, breaking the atomic `inserted`/`updated` contract consumed by `WalletEvent::BlockUpdate`. Capture `existed_before` per account and route to `new_records` when the record was just created.

Addresses CodeRabbit review comment on PR #696
#696 (comment)

* refactor(key-wallet-manager): extract `finalize_block_advance` helper

`process_block` and `update_last_processed_height` duplicated the entire balance-snapshot, prior-heights collection, matured-coinbase window, height advance, and per-wallet `BlockUpdate` emission. Extract the shared tail into a private `WalletManager::finalize_block_advance` helper that takes the inserted/updated maps. `update_last_processed_height` becomes a one-line call with empty maps; `process_block` keeps only its txdata loop before delegating.

* refactor: rename wallet events for clearer semantics

Rename `WalletEvent` variants and the matching FFI callbacks to past-participle names that say what happened, replacing vague "Update" suffixes:

- `TransactionReceived` -> `TransactionDetected`. "Received" implied incoming funds, but the event fires for any first-time off-chain sighting (incoming or outgoing).
- `TransactionStatusChanged` -> `TransactionInstantLocked`. The event only ever fires for an InstantSend lock applied to a known mempool tx, so name it for what it actually is. Drop the `status: TransactionContext` field and carry the `InstantLock` directly.
- `BlockUpdate` -> `BlockProcessed`. Mirrors `process_block` and matches the past-participle pattern.
- `SyncHeightUpdate` -> `SyncHeightAdvanced`. Conveys monotonic forward motion.

FFI rename mirrors the Rust side: the IS callback now takes `islock_data: *const u8` + `islock_len: usize` instead of an `FFITransactionContext`, removing a discriminant that was always `InstantSend`. The wallet-side `OnBlockProcessedCallback` becomes `OnWalletBlockProcessedCallback` to disambiguate from the existing sync-event type with the same name.

* fix: record balance before bumping IS-locked counter in test callback

Addresses CodeRabbit review comment on PR #696
#696 (review)

The instant_locked callback bumped `transaction_instant_send_locked_count` before calling `record_balance`. Tests that wait on the counter and then read `last_confirmed`/`last_unconfirmed` could observe the previous balance snapshot. Match the ordering used by the other callbacks: store the balance first, then bump the counter.

* fix: backfill missing transaction record in InstantSend path

Addresses CodeRabbit review comment on PR #696
#696 (review)

The IS-lock branch in `WalletTransactionChecker::check_core_transaction` only updated accounts that already held a `TransactionRecord` for the txid. When wallet-level `is_new` was `false` (because at least one account had the record) but another matched account did not, the latter was silently skipped: no record was created and `mark_utxos_instant_send` ran against an empty UTXO set on that account.

Mirror the confirmation path: when the affected account lacks the record, call `record_transaction` to register the record and its UTXOs, then mark them IS-locked. This ordering ensures the freshly registered UTXOs receive the IS-lock flag too. The backfilled record is pushed into `new_records` to match the existing convention from commit 659a6d5.

Add `test_instantsend_backfills_missing_record_in_other_account` covering the multi-account scenario.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: QuantumExplorer <11468583+QuantumExplorer@users.noreply.github.com>
Co-authored-by: Kevin Rombach <35775977+xdustinface@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: QuantumExplorer <quantum@dash.org>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: QuantumExplorer <11468583+QuantumExplorer@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants