Skip to content

refactor(platform-wallet): adopt rust-dashcore wallet event-bus API#3556

Merged
QuantumExplorer merged 1 commit intov3.1-devfrom
feat/per-wallet-filter-scan-deps
Apr 28, 2026
Merged

refactor(platform-wallet): adopt rust-dashcore wallet event-bus API#3556
QuantumExplorer merged 1 commit intov3.1-devfrom
feat/per-wallet-filter-scan-deps

Conversation

@QuantumExplorer
Copy link
Copy Markdown
Member

@QuantumExplorer QuantumExplorer commented Apr 28, 2026

Issue being fixed or feature implemented

rust-dashcore PR #696 — feat: make wallet events atomic deleted key_wallet::changeset::WalletChangeSet and key_wallet_manager::WalletPersistence in favour of a broadcast::Sender<WalletEvent> bus where every variant carries the post-change WalletCoreBalance plus the records affected (TransactionDetected, TransactionInstantLocked, BlockProcessed { inserted, updated, matured }, SyncHeightAdvanced). The platform side leaned heavily on both removed types and stopped compiling after the upstream merge.

This PR re-points the workspace's rust-dashcore deps at the merge commit ea33cbc84179666c25515dfc817ce32210953037 and adapts platform-wallet to the new model. Same persistence shape from the client's perspective; the source of truth on the Rust side moves from a callback to an event subscriber.

What was done?

Architecture preserved. PlatformWalletChangeSet, Merge, apply.rs, the persister trait, and the Swift FFI surface (WalletChangeSetFFI) all keep their structure. Only PlatformWalletChangeSet.core's field type changes.

New CoreChangeSet — platform-owned projection of WalletEvent data. Replaces the deleted key_wallet::changeset::WalletChangeSet:

pub struct CoreChangeSet {
    pub records: Vec<TransactionRecord>,
    pub spent_utxos: Vec<Utxo>,
    pub new_utxos: Vec<Utxo>,
    pub instant_locks_for_non_final_records: BTreeMap<Txid, InstantLock>,
    pub last_processed_height: Option<u32>,
    pub synced_height: Option<u32>,
}

Chain-locked records skip the IS-lock map (chain-lock supersedes IS finality). Merge impl uses monotonic-max for the height watermarks, append-only for the vecs, last-write-wins for the IS-lock map.

New spawn_wallet_event_adapter replaces CorePersistenceBridge. Subscribes to WalletManager::subscribe_events() from a tokio task, projects each event into a CoreChangeSet (deriving UTXO deltas from each record's input_details / output_details), wraps as PlatformWalletChangeSet { core: Some(cs), .. }, forwards to PlatformWalletPersistence::store. Decouples persistence from SPV — the previous bridge ran the persister callback inside SPV's own write lock, so a slow SwiftData transaction stalled block processing. The broadcast channel buffers events; the adapter task drains them off the SPV path.

Updated for the new upstream API:

  • WalletInfoInterface impl on PlatformWalletInfo: adds get_spendable_utxos, last_processed_height, update_last_processed_height, matured_coinbase_records. update_balance returns (). mark_instant_send_utxos returns bool.
  • SpvRuntime: DashSpvClient<W, N, S> is now 3 generics; the old EventManager 4th generic became an event_handlers: Vec<Arc<dyn EventHandler>> field. PlatformEventManager registers as one of those dyn handlers.
  • BalanceUpdateHandler: reads balance from the per-event snapshot (TransactionDetected / TransactionInstantLocked / BlockProcessed) instead of the removed BalanceUpdated variant.
  • 8 next_*_address callsites get the new add_to_state: bool arg (true for transaction-build sites; false for asset-lock building, which marks-as-used itself inside the builder).
  • FFI: WalletChangeSetFFI::from_changeset(&CoreChangeSet) buckets records by account_type and derives per-account UTXO deltas at FFI time (linear find/insert because AccountType isn't Ord upstream).
  • FFI manager constructor enters the shared tokio runtime via runtime().enter() so the event-adapter task spawn lands on it (Swift calls in synchronously, no reactor in scope).
  • PlatformWalletManager gains a shutdown() method that cancels and joins the adapter task.

SwiftData schema (packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/):

  • PersistentTransaction gains @Relationship(deleteRule: .cascade, inverse: \PersistentUtxo.transaction) var utxos: [PersistentUtxo].
  • PersistentUtxo drops the redundant stored txid: String; gains the inverse var transaction: PersistentTransaction?. A computed txid property delegates through the relationship so existing readers (UtxoStorageDetailView) keep working without churn.
  • upsertUtxo looks up the parent PersistentTransaction (already in context — upstream sends transactions before their UTXOs in the same flush) and links it with defensive stub creation if missing.

Swift FFI rename. Upstream renamed the FFI account discriminant enum from FFIAccountType to FFIAccountKind and reused the FFIAccountType name for a richer struct that bundles the discriminant with index / Dashpay-pointer / key-class fields. AccountType.ffiValue and init(ffiType:) updated to FFIAccountKind.

TODO(events) follow-ups left in code

  • instant_locks_for_non_final_records is not yet wired through the FFI surface. The Swift side learns about IS-lock state only when re-emitted records flow through records with context = InstantSend(..). When a standalone IS-lock event needs to reach Swift, add a corresponding FFI field and populate it.
  • AccountType::IdentityAuthenticationEcdsa / IdentityAuthenticationBls were removed upstream; the FFI tags remain in the ABI for back-compat but mapping back to a key-wallet AccountType errors with a TODO(events) marker until identity-key derivation moves off the wallet AccountType entirely.

How Has This Been Tested?

  • cargo check --workspace — clean
  • cargo fmt --all -- --check — clean
  • ./packages/swift-sdk/build_ios.sh --target sim — Rust workspace + xcframework + SwiftDashSDK + SwiftExampleApp all build end-to-end
  • App boots through PlatformWalletManager::new without the tokio "no reactor" panic that an early iteration hit (fix landed: enter the FFI's shared runtime around the constructor)

Breaking Changes

None at the consensus layer. Public API churn in platform-wallet (the core field's type) and platform-wallet-ffi (the from_changeset input type, the deprecated identity-auth AccountType mapping) — all internal to this repo.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any (N/A — no consensus-breaking changes)
  • I have made corresponding changes to the documentation if needed

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added wallet manager shutdown method for graceful cleanup
  • Bug Fixes

    • Fixed wallet initialization panic related to async runtime task spawning
  • Refactor

    • Improved wallet persistence and event handling architecture
    • Enhanced UTXO and transaction relationship tracking
    • Updated wallet event projection model
    • Removed support for deprecated account types in persistence layer

Upstream rust-dashcore PR #696 deleted `WalletChangeSet` and
`WalletPersistence` in favour of a `broadcast::Sender<WalletEvent>` bus
where each event carries the post-change balance and records.

Re-points `rust-dashcore` deps at the merge commit ea33cbc84179666c25515dfc817ce32210953037
and adapts platform-wallet:

* New `CoreChangeSet` (platform-owned projection of `WalletEvent` data)
  replaces `WalletChangeSet` in `PlatformWalletChangeSet.core` — the
  persister/merge/apply surfaces stay identical, only the field type
  changed.
* New `spawn_wallet_event_adapter` (replaces `CorePersistenceBridge`)
  drains the upstream broadcast and ships projected changesets through
  the platform persister. Decouples persistence from SPV's write lock —
  a slow Swift persister no longer stalls block processing.
* `WalletInfoInterface` impl, `SpvRuntime` (3 generics, was 4),
  `BalanceUpdateHandler`, FFI conversion all updated to the new shapes.
* SwiftData: `PersistentTransaction` cascade-deletes `PersistentUtxo`;
  `PersistentUtxo.txid` is now a computed property reading through the
  relationship.
* Swift FFI: `AccountType.ffiValue` returns `FFIAccountKind` (upstream
  renamed the discriminant enum and reused `FFIAccountType` for a
  richer struct).
* FFI manager constructor enters the shared tokio runtime so the
  event-adapter task spawn lands on it.

TODO(events) markers in the diff flag follow-up work:
* IS-lock standalone events not yet wired through the FFI.
* `IdentityAuthenticationEcdsa`/`Bls` AccountType variants were removed
  upstream; FFI tags need new mapping once identity-key derivation
  moves off `AccountType`.

Verified: `cargo check --workspace` clean, `cargo fmt --all` clean, iOS
simulator framework + SwiftExampleApp build succeed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v3.1.0 milestone Apr 28, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Workspace dependencies are updated to a new rust-dashcore commit. Core wallet persistence architecture shifts from callback-based bridge to async event adapter model. A new CoreChangeSet struct replaces upstream changeset types. Multiple address derivation calls receive an additional boolean parameter. Swift SDK models restructure UTXO persistence to use explicit transaction relationships.

Changes

Cohort / File(s) Summary
Workspace Dependencies
Cargo.toml
Updates git revision pins for dashcore, dash-spv, dash-spv-ffi, key-wallet, key-wallet-ffi, key-wallet-manager, and dashcore-rpc from 4c8bec... to ea33cbc...
Core Wallet Changesets
packages/rs-platform-wallet/src/changeset/changeset.rs, packages/rs-platform-wallet/src/changeset/core_bridge.rs, packages/rs-platform-wallet/src/changeset/mod.rs
Introduces CoreChangeSet struct to replace upstream WalletChangeSet. Replaces callback-based CorePersistenceBridge with spawn_wallet_event_adapter that subscribes to wallet events, converts them to changesets, and forwards to persistence. Re-exports updated to reflect new adapter public API.
FFI Core Wallet Types
packages/rs-platform-wallet-ffi/src/core_wallet_types.rs
WalletChangeSetFFI::from_changeset now accepts platform_wallet::changeset::CoreChangeSet. Balance delta and block hash transport eliminated. Chain data reduced to synced height only. Records bucketed by account type with UTXO derivation at conversion time.
FFI Manager & Runtime
packages/rs-platform-wallet-ffi/src/manager.rs, packages/rs-platform-wallet/src/spv/runtime.rs
FFI manager constructor explicitly enters shared Tokio runtime before PlatformWalletManager::new. SPV client initialization adjusted to accept explicit event handler vector instead of handler type parameter.
Account Type & Persistence
packages/rs-platform-wallet-ffi/src/persistence.rs
IdentityAuthenticationEcdsa/IdentityAuthenticationBls account type tags are no longer produced; load path now fails with PersistenceError if supplied.
Wallet Manager Lifecycle
packages/rs-platform-wallet/src/manager/mod.rs
Removes CorePersistenceBridge wiring. Spawns spawn_wallet_event_adapter task with CancellationToken and JoinHandle. Adds new shutdown async method for clean adapter cancellation and join.
Wallet Traits & Apply
packages/rs-platform-wallet/src/wallet/platform_wallet_traits.rs, packages/rs-platform-wallet/src/wallet/apply.rs
update_balance and mark_instant_send_utxos return types simplified (void and bool respectively). New height/UTXO APIs added: get_spendable_utxos, last_processed_height, update_last_processed_height, matured_coinbase_records. Core changeset no longer replayed in apply_changeset.
Address Derivation Updates
packages/rs-platform-wallet/src/wallet/core/wallet.rs, packages/rs-platform-wallet/src/wallet/core/broadcast.rs, packages/rs-platform-wallet/src/wallet/asset_lock/build.rs, packages/rs-platform-wallet/src/wallet/identity/network/payments.rs, packages/rs-platform-wallet/src/wallet/platform_addresses/wallet.rs
All calls to next_address, next_receive_address, and next_change_address now pass an additional boolean argument (true).
Balance Handler
packages/rs-platform-wallet/src/wallet/core/balance_handler.rs
Event routing expanded from BalanceUpdated only to TransactionDetected, TransactionInstantLocked, BlockProcessed. SyncHeightAdvanced explicitly ignored. Balance updates use accessor methods on extracted snapshot.
Swift SDK FFI Bridging
packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/KeyWalletTypes.swift
AccountType FFI bridging layer updated: ffiValue now exposes FFIAccountKind instead of FFIAccountType; initializer parameter type changed accordingly.
Swift SDK Persistence Models
packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentTransaction.swift, packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentUtxo.swift, packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift
PersistentUtxo migrated to explicit transaction relationship (cascade-delete). txid becomes computed property derived from relationship. Persistence handler fetches/creates stub transactions to maintain cascade invariant.

Sequence Diagram

sequenceDiagram
    participant WM as WalletManager
    participant EA as EventAdapter<br/>(spawn_wallet_event_adapter)
    participant PERSIST as PlatformWalletPersistence
    
    rect rgba(100, 150, 200, 0.5)
        Note over WM, EA: Event Adapter Subscription Loop
        activate EA
        WM->>WM: Emits WalletEvent
        WM->>EA: Broadcasts WalletEvent<br/>(via stream)
        EA->>EA: Subscribe to event stream
    end
    
    rect rgba(150, 200, 100, 0.5)
        Note over EA, PERSIST: Event→CoreChangeSet Conversion
        EA->>EA: Extract wallet_id, balance,<br/>transactions, instant locks
        EA->>EA: Derive UTXO created/spent sets<br/>from record projections
        EA->>EA: Build CoreChangeSet<br/>(records, utxos, heights)
        EA->>EA: Check emptiness to avoid<br/>unnecessary persist calls
    end
    
    rect rgba(200, 150, 100, 0.5)
        Note over EA, PERSIST: Changeset Persistence
        EA->>PERSIST: Wrap CoreChangeSet in<br/>PlatformWalletChangeSet
        PERSIST->>PERSIST: store() persists state
        PERSIST-->>EA: Acknowledgment
        EA->>EA: Continue listening for<br/>next event
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Poem

🐰 Hark! The wallet events flow free,
Async streams of harmony,
No callbacks bind, just pure events dance,
Transactions caught in adapter's glance,
Persistence blooms through broadcasts bright! 🌸

🚥 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 accurately describes the main refactoring: adopting the rust-dashcore wallet event-bus API, which is the central change across all modified files.
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/per-wallet-filter-scan-deps

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.

@thepastaclaw
Copy link
Copy Markdown
Collaborator

thepastaclaw commented Apr 28, 2026

Review Gate

Commit: 5efb2bf3

  • Debounce: 4m ago (need 30m)

  • CI checks: checks still running (2 pending)

  • CodeRabbit review: comment found

  • Off-peak hours: off-peak (03:15 AM PT Tuesday)

  • Run review now (check to override)

Copy link
Copy Markdown
Member Author

@QuantumExplorer QuantumExplorer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self Reviewed

@QuantumExplorer QuantumExplorer merged commit ddf64eb into v3.1-dev Apr 28, 2026
14 of 15 checks passed
@QuantumExplorer QuantumExplorer deleted the feat/per-wallet-filter-scan-deps branch April 28, 2026 10:20
@github-actions
Copy link
Copy Markdown
Contributor

✅ DashSDKFFI.xcframework built for this PR.

SwiftPM (host the zip at a stable URL, then use):

.binaryTarget(
  name: "DashSDKFFI",
  url: "https://your.cdn.example/DashSDKFFI.xcframework.zip",
  checksum: "e91e96312f678e32dca1d802545572bc6579212caea2e7aa401495b84af827d8"
)

Xcode manual integration:

  • Download 'DashSDKFFI.xcframework' artifact from the run link above.
  • Drag it into your app target (Frameworks, Libraries & Embedded Content) and set Embed & Sign.
  • If using the Swift wrapper package, point its binaryTarget to the xcframework location or add the package and place the xcframework at the expected path.

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