Skip to content

Commit 66c3ace

Browse files
shumkovclaude
andcommitted
feat(platform-wallet): use dashcore WalletManager directly, delete SpvWalletAdapter
PR-30 Phase 2: Replace custom SpvWalletAdapter with dashcore's WalletManager<PlatformWalletInfo> for SPV integration. Key changes: - PlatformWalletInfo uses ManagedWalletState<PlatformWalletPersisterBridge> for automatic changeset persistence during check_core_transaction (C2) - Arc<WalletBalance> shared between PlatformWalletInfo and CoreWallet for lock-free balance reads; updated via BalanceUpdated events (C1) - Delete SpvWalletAdapter (~330 lines) — WalletManager implements WalletInterface directly - Delete SpvSyncState (~55 lines) — WalletManager tracks heights - Delete PlatformWalletInfoWriteGuard — balance via events not Drop - SpvRuntime holds Arc<RwLock<WalletManager<PlatformWalletInfo>>> - PlatformWalletManager shares Arc<RwLock<PlatformWalletInfo>> between WalletManager (SPV) and PlatformWallet handles (sub-wallets) - wallets map behind RwLock for interior mutability (&self methods) (W3) - Trait impls split to platform_wallet_traits.rs (W8) - Remove dead CoreWallet state accessors (I1) - PlatformWalletPersisterBridge bridges WalletChangeSet to PlatformWalletChangeSet for persistence - Remove notify_wallets_changed (W5) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cb755c6 commit 66c3ace

19 files changed

Lines changed: 660 additions & 622 deletions

File tree

packages/rs-platform-wallet/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ platform-encryption = { path = "../rs-platform-encryption" }
1414

1515
# Key wallet dependencies (from rust-dashcore)
1616
key-wallet = { workspace = true }
17-
key-wallet-manager = { workspace = true }
17+
key-wallet-manager = { workspace = true, features = [] }
1818
dash-spv = { workspace = true }
1919

2020
# Core dependencies
@@ -51,6 +51,6 @@ static_assertions = "1.1"
5151

5252
[features]
5353
default = ["bls", "eddsa"]
54-
bls = ["key-wallet/bls"]
55-
eddsa = ["key-wallet/eddsa"]
54+
bls = ["key-wallet/bls", "key-wallet-manager/bls"]
55+
eddsa = ["key-wallet/eddsa", "key-wallet-manager/eddsa"]
5656
shielded = ["dep:grovedb-commitment-tree", "dep:zip32", "dash-sdk/shielded", "dpp/shielded-client"]

packages/rs-platform-wallet/examples/basic_usage.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use std::sync::Arc;
77

88
use dash_sdk::Sdk;
9-
use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface;
109
use key_wallet::Network;
1110
use platform_wallet::changeset::PlatformWalletPersistence;
1211
use platform_wallet::error::PlatformWalletError;
@@ -74,10 +73,10 @@ fn main() -> Result<(), PlatformWalletError> {
7473
// All mutable state is behind a single lock — one acquisition gives
7574
// access to everything.
7675
{
77-
let info = core.state_blocking();
78-
let utxos = info.wallet_info.get_spendable_utxos();
79-
let tx_count = info.wallet_info.transaction_history().len();
80-
let birth = info.wallet_info.birth_height();
76+
let info = wallet.state_blocking();
77+
let utxos = info.managed_state.wallet_info().get_spendable_utxos();
78+
let tx_count = info.managed_state.wallet_info().transaction_history().len();
79+
let birth = info.managed_state.wallet_info().birth_height();
8180
let id_count = info.identity_manager.identities().len();
8281
println!("UTXOs: {}, transactions: {}, birth_height: {}", utxos.len(), tx_count, birth);
8382
println!("Managed identities: {}", id_count);

packages/rs-platform-wallet/src/manager.rs

Lines changed: 73 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
//! Multi-wallet manager with SPV coordination.
22
3-
use std::collections::BTreeMap;
43
use std::sync::Arc;
54

65
use tokio::sync::{broadcast, RwLock};
76

87
use key_wallet::wallet::initialization::WalletAccountCreationOptions;
98
use key_wallet::Network;
9+
use key_wallet_manager::WalletManager;
1010

1111
use crate::changeset::{Merge, PlatformWalletPersistence};
1212

@@ -23,8 +23,10 @@ pub struct WalletCreationOptions {
2323
use crate::error::PlatformWalletError;
2424
use crate::events::PlatformWalletEvent;
2525
use crate::spv::SpvRuntime;
26-
use crate::wallet::platform_wallet::WalletId;
26+
use crate::wallet::persister::PlatformWalletPersisterBridge;
27+
use crate::wallet::platform_wallet::{PlatformWalletInfo, WalletId};
2728
use crate::wallet::PlatformWallet;
29+
use crate::wallet::core::WalletBalance;
2830

2931
/// Multi-wallet coordinator with SPV sync and event broadcasting.
3032
///
@@ -34,13 +36,20 @@ use crate::wallet::PlatformWallet;
3436
/// broadcasts unified [`PlatformWalletEvent`]s (sync progress, network
3537
/// changes, wallet updates, finality proofs) to subscribers.
3638
///
37-
/// Each managed [`PlatformWallet`] shares its underlying `Wallet` and
38-
/// `ManagedWalletInfo` with the SPV adapter through `Arc<RwLock<…>>`,
39-
/// so balance and UTXO updates from SPV are immediately visible to all
40-
/// wallet operations.
39+
/// Internally holds a `WalletManager<PlatformWalletInfo>` that implements
40+
/// `WalletInterface` for DashSpvClient, and a separate map of
41+
/// `PlatformWallet` handles. Both share the same `Arc<RwLock<PlatformWalletInfo>>`
42+
/// per wallet, so balance and UTXO updates from SPV are immediately visible
43+
/// to all wallet operations.
4144
pub struct PlatformWalletManager {
4245
sdk: Arc<dash_sdk::Sdk>,
43-
wallets: Arc<RwLock<BTreeMap<WalletId, Arc<PlatformWallet>>>>,
46+
/// Core-layer wallet manager implementing `WalletInterface`.
47+
/// Shared with `SpvRuntime` so DashSpvClient drives block/mempool
48+
/// processing directly through it.
49+
wallet_manager: Arc<RwLock<WalletManager<PlatformWalletInfo>>>,
50+
/// Platform-level wallet handles (sub-wallets, identity, dashpay, etc.).
51+
/// Interior mutability via `RwLock` so methods take `&self`.
52+
wallets: RwLock<std::collections::BTreeMap<WalletId, Arc<PlatformWallet>>>,
4453
event_tx: broadcast::Sender<PlatformWalletEvent>,
4554
spv: Arc<SpvRuntime>,
4655
persister: Arc<dyn PlatformWalletPersistence>,
@@ -50,11 +59,15 @@ impl PlatformWalletManager {
5059
/// Create a new PlatformWalletManager.
5160
pub fn new(sdk: Arc<dash_sdk::Sdk>, persister: Arc<dyn PlatformWalletPersistence>) -> Self {
5261
let (event_tx, _) = broadcast::channel(256);
53-
let wallets = Arc::new(RwLock::new(BTreeMap::new()));
54-
let spv = Arc::new(SpvRuntime::new(Arc::clone(&wallets), event_tx.clone()));
62+
let wallet_manager = Arc::new(RwLock::new(WalletManager::new(sdk.network)));
63+
let spv = Arc::new(SpvRuntime::new(
64+
Arc::clone(&wallet_manager),
65+
event_tx.clone(),
66+
));
5567
Self {
5668
sdk,
57-
wallets,
69+
wallet_manager,
70+
wallets: RwLock::new(std::collections::BTreeMap::new()),
5871
event_tx,
5972
spv,
6073
persister,
@@ -98,9 +111,9 @@ impl PlatformWalletManager {
98111
seed_bytes: [u8; 64],
99112
options: WalletCreationOptions,
100113
) -> Result<Arc<PlatformWallet>, PlatformWalletError> {
101-
use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface;
102114
use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo;
103115
use key_wallet::wallet::Wallet;
116+
use key_wallet_manager::ManagedWalletState;
104117

105118
let wallet =
106119
Wallet::from_seed_bytes(seed_bytes, network, options.accounts).map_err(|e| {
@@ -115,11 +128,40 @@ impl PlatformWalletManager {
115128
}
116129
let wallet_id = wallet_info.wallet_id;
117130

131+
// Build ManagedWalletState with the persister bridge.
132+
let bridge = PlatformWalletPersisterBridge::new(wallet_id, Arc::clone(&self.persister));
133+
let managed_state = ManagedWalletState::new(wallet, wallet_info, bridge);
134+
let balance = Arc::new(WalletBalance::new());
135+
136+
// Build the shared state Arc.
137+
let state = Arc::new(RwLock::new(PlatformWalletInfo {
138+
managed_state,
139+
balance: Arc::clone(&balance),
140+
identity_manager: crate::wallet::identity::IdentityManager::new(),
141+
tracked_asset_locks: std::collections::BTreeMap::new(),
142+
platform_address_balances: std::collections::BTreeMap::new(),
143+
token_watched: std::collections::BTreeMap::new(),
144+
token_balances: std::collections::BTreeMap::new(),
145+
}));
146+
147+
// Insert into WalletManager (shares the same Arc).
148+
{
149+
let mut wm = self.wallet_manager.write().await;
150+
wm.insert_wallet_state(wallet_id, Arc::clone(&state))
151+
.map_err(|e| {
152+
PlatformWalletError::WalletCreation(format!(
153+
"Failed to register wallet in WalletManager: {}",
154+
e
155+
))
156+
})?;
157+
}
158+
159+
// Build the PlatformWallet handle from the shared state.
118160
let broadcaster = Arc::new(crate::broadcaster::SpvBroadcaster::new(Arc::clone(&self.spv)));
119-
let platform_wallet = PlatformWallet::new(
161+
let platform_wallet = PlatformWallet::from_shared_state(
120162
Arc::clone(&self.sdk),
121-
wallet,
122-
wallet_info,
163+
wallet_id,
164+
state,
123165
self.event_tx.clone(),
124166
Arc::clone(&self.persister),
125167
broadcaster,
@@ -134,19 +176,15 @@ impl PlatformWalletManager {
134176
})?;
135177
if !changeset.is_empty() {
136178
platform_wallet.apply(&changeset);
137-
// TODO: Once apply() actually restores wallet state (transactions,
138-
// UTXOs) from the changeset, set birth_height from the persisted
139-
// chain height here so SPV doesn't rescan from genesis on restart.
140-
// Until then, birth_height must come from WalletCreationOptions.
141179
}
142180

143181
let platform_wallet = Arc::new(platform_wallet);
144182

145-
// Register with the manager so SPV processes this wallet.
146-
let mut wallets = self.wallets.write().await;
147-
wallets.insert(wallet_id, Arc::clone(&platform_wallet));
148-
drop(wallets);
149-
self.spv.notify_wallets_changed();
183+
// Register the PlatformWallet handle.
184+
{
185+
let mut wallets = self.wallets.write().await;
186+
wallets.insert(wallet_id, Arc::clone(&platform_wallet));
187+
}
150188

151189
Ok(platform_wallet)
152190
}
@@ -156,11 +194,18 @@ impl PlatformWalletManager {
156194
&self,
157195
wallet_id: &WalletId,
158196
) -> Result<Arc<PlatformWallet>, PlatformWalletError> {
159-
let mut wallets = self.wallets.write().await;
160-
let removed = wallets
161-
.remove(wallet_id)
162-
.ok_or_else(|| PlatformWalletError::WalletNotFound(hex::encode(wallet_id)))?;
163-
self.spv.notify_wallets_changed();
197+
// Remove from PlatformWallet handles.
198+
let removed = {
199+
let mut wallets = self.wallets.write().await;
200+
wallets
201+
.remove(wallet_id)
202+
.ok_or_else(|| PlatformWalletError::WalletNotFound(hex::encode(wallet_id)))?
203+
};
204+
// Remove from WalletManager.
205+
{
206+
let mut wm = self.wallet_manager.write().await;
207+
let _ = wm.remove_wallet(wallet_id);
208+
}
164209
Ok(removed)
165210
}
166211

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
mod event_forwarder;
22
mod runtime;
3-
mod sync_state;
4-
mod wallet_adapter;
53

64
pub use runtime::SpvRuntime;

packages/rs-platform-wallet/src/spv/runtime.rs

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
//! Asset-lock finality tracking (IS/CL proof waiting) is handled by
77
//! `AssetLockManager` directly — it subscribes to the shared event channel.
88
9-
use std::collections::BTreeMap;
109
use std::sync::Arc;
1110

1211
use tokio::sync::{broadcast, RwLock};
@@ -19,63 +18,55 @@ use dash_spv::network::PeerNetworkManager;
1918
use dash_spv::storage::DiskStorageManager;
2019
use dash_spv::{ClientConfig, DashSpvClient, Hash};
2120

22-
use key_wallet_manager::WalletInterface;
21+
use key_wallet_manager::{WalletInterface, WalletManager};
2322

2423
use crate::error::PlatformWalletError;
2524
use crate::events::PlatformWalletEvent;
2625
use crate::spv::event_forwarder::SpvEventForwarder;
27-
use crate::spv::wallet_adapter::SpvWalletAdapter;
28-
use crate::wallet::platform_wallet::WalletId;
29-
use crate::wallet::PlatformWallet;
26+
use crate::wallet::platform_wallet::PlatformWalletInfo;
3027

3128
type SpvClient =
32-
DashSpvClient<SpvWalletAdapter, PeerNetworkManager, DiskStorageManager, SpvEventForwarder>;
29+
DashSpvClient<WalletManager<PlatformWalletInfo>, PeerNetworkManager, DiskStorageManager, SpvEventForwarder>;
3330

3431
/// SPV client runtime — owns the `DashSpvClient` and tracks sync height.
3532
///
36-
/// Holds references to the wallets collection and event channel at construction
37-
/// time, so callers just need `start(config)` / `stop()`.
33+
/// Holds a reference to the shared `WalletManager<PlatformWalletInfo>` and
34+
/// event channel at construction time, so callers just need `start(config)` /
35+
/// `stop()`.
3836
///
3937
/// Asset-lock finality tracking (InstantLock / ChainLock waiting) is handled
4038
/// directly by `AssetLockManager` via SPV event subscriptions — the runtime
4139
/// only drives SPV sync and forwards events.
4240
pub struct SpvRuntime {
4341
event_tx: broadcast::Sender<PlatformWalletEvent>,
44-
/// Shared sync state — atomics accessible without holding the adapter lock.
45-
sync_state: Arc<super::sync_state::SpvSyncState>,
46-
adapter: Arc<RwLock<SpvWalletAdapter>>,
42+
/// Shared `WalletManager<PlatformWalletInfo>` — implements `WalletInterface`,
43+
/// so DashSpvClient can drive block/mempool processing directly through it.
44+
/// `WalletManager` bumps its own structural revision when wallets are
45+
/// added/removed, so no external `notify_wallets_changed()` is needed.
46+
wallet_manager: Arc<RwLock<WalletManager<PlatformWalletInfo>>>,
4747
client: RwLock<Option<SpvClient>>,
4848
}
4949

5050
impl SpvRuntime {
51-
/// Create a new SPV runtime bound to a wallets collection and event channel.
51+
/// Create a new SPV runtime bound to a wallet manager and event channel.
5252
pub fn new(
53-
wallets: Arc<RwLock<BTreeMap<WalletId, Arc<PlatformWallet>>>>,
53+
wallet_manager: Arc<RwLock<WalletManager<PlatformWalletInfo>>>,
5454
event_tx: broadcast::Sender<PlatformWalletEvent>,
5555
) -> Self {
56-
let sync_state = Arc::new(super::sync_state::SpvSyncState::new());
57-
let adapter = Arc::new(RwLock::new(SpvWalletAdapter::new(
58-
wallets,
59-
Arc::clone(&sync_state),
60-
)));
6156
Self {
6257
event_tx,
63-
sync_state,
64-
adapter,
58+
wallet_manager,
6559
client: RwLock::new(None),
6660
}
6761
}
6862

69-
/// Current synced height. Always returns the correct value even during
70-
/// block processing (atomics are outside the adapter's RwLock).
63+
/// Current synced height. Reads a plain field on WalletManager (sync, no
64+
/// per-wallet lock).
7165
pub fn synced_height(&self) -> u32 {
72-
self.sync_state.synced_height()
73-
}
74-
75-
/// Signal that the wallet set changed (added/removed).
76-
/// SPV will rebuild the bloom filter on the next tick.
77-
pub fn notify_wallets_changed(&self) {
78-
self.sync_state.bump_monitor_revision();
66+
self.wallet_manager
67+
.try_read()
68+
.map(|wm| wm.synced_height())
69+
.unwrap_or(0)
7970
}
8071

8172
/// Reset filter_committed_height to 0, forcing a filter rescan from
@@ -84,8 +75,9 @@ impl SpvRuntime {
8475
/// Useful when wallet state isn't persisted: cached committed height
8576
/// from a previous run would skip historical blocks, leaving the
8677
/// wallet with zero balance.
87-
pub fn reset_filter_committed_height(&self) {
88-
self.sync_state.update_filter_committed_height(0);
78+
pub async fn reset_filter_committed_height(&self) {
79+
let mut wm = self.wallet_manager.write().await;
80+
wm.update_filter_committed_height(0).await;
8981
}
9082

9183
/// Start SPV sync.
@@ -110,7 +102,7 @@ impl SpvRuntime {
110102
config,
111103
network_manager,
112104
storage_manager,
113-
Arc::clone(&self.adapter),
105+
Arc::clone(&self.wallet_manager),
114106
Arc::new(forwarder),
115107
)
116108
.await

packages/rs-platform-wallet/src/spv/sync_state.rs

Lines changed: 0 additions & 54 deletions
This file was deleted.

0 commit comments

Comments
 (0)