Skip to content

Commit 6673a1a

Browse files
committed
fix: make sure kyoto tasks killed when switching networks
1 parent e8c9eb6 commit 6673a1a

File tree

3 files changed

+53
-73
lines changed

3 files changed

+53
-73
lines changed

BDKSwiftExampleWallet/Extensions/BDK+Extensions/CbfClient+Extensions.swift

Lines changed: 31 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import BitcoinDevKit
99
import Foundation
1010

1111
extension CbfClient {
12+
// Track one monitoring task per client for clean cancellation
13+
private static var monitoringTasks: [ObjectIdentifier: Task<Void, Never>] = [:]
14+
private static let monitoringTasksQueue = DispatchQueue(label: "cbf.monitoring.tasks")
15+
1216
static func createComponents(wallet: Wallet) -> (client: CbfClient, node: CbfNode) {
1317
do {
1418
let components = try CbfBuilder()
@@ -20,13 +24,6 @@ extension CbfClient {
2024

2125
components.node.run()
2226

23-
// Send initial 1% progress after successful node startup
24-
NotificationCenter.default.post(
25-
name: NSNotification.Name("KyotoProgressUpdate"),
26-
object: nil,
27-
userInfo: ["progress": Float(1)]
28-
)
29-
3027
components.client.startBackgroundMonitoring()
3128

3229
return (client: components.client, node: components.node)
@@ -36,16 +33,14 @@ extension CbfClient {
3633
}
3734

3835
func startBackgroundMonitoring() {
39-
Task {
40-
while true {
41-
if let log = try? await self.nextLog() { }
42-
}
43-
}
36+
let id = ObjectIdentifier(self)
4437

45-
Task {
38+
let task = Task { [self] in
4639
var hasEstablishedConnection = false
4740
while true {
48-
if let info = try? await self.nextInfo() {
41+
if Task.isCancelled { break }
42+
do {
43+
let info = try await self.nextInfo()
4944
switch info {
5045
case let .progress(progress):
5146
await MainActor.run {
@@ -62,7 +57,6 @@ extension CbfClient {
6257
object: nil,
6358
userInfo: ["height": height]
6459
)
65-
6660
if !hasEstablishedConnection {
6761
hasEstablishedConnection = true
6862
NotificationCenter.default.post(
@@ -72,16 +66,7 @@ extension CbfClient {
7266
)
7367
}
7468
}
75-
case .connectionsMet:
76-
await MainActor.run {
77-
hasEstablishedConnection = true
78-
NotificationCenter.default.post(
79-
name: NSNotification.Name("KyotoConnectionUpdate"),
80-
object: nil,
81-
userInfo: ["connected": true]
82-
)
83-
}
84-
case .successfulHandshake:
69+
case .connectionsMet, .successfulHandshake:
8570
await MainActor.run {
8671
if !hasEstablishedConnection {
8772
hasEstablishedConnection = true
@@ -95,27 +80,31 @@ extension CbfClient {
9580
default:
9681
break
9782
}
83+
} catch is CancellationError {
84+
break
85+
} catch {
86+
// ignore
9887
}
9988
}
10089
}
10190

102-
Task {
103-
while true {
104-
if let warning = try? await self.nextWarning() {
105-
switch warning {
106-
case .needConnections:
107-
await MainActor.run {
108-
NotificationCenter.default.post(
109-
name: NSNotification.Name("KyotoConnectionUpdate"),
110-
object: nil,
111-
userInfo: ["connected": false]
112-
)
113-
}
114-
default:
115-
break
116-
}
117-
}
118-
}
91+
Self.monitoringTasksQueue.sync {
92+
Self.monitoringTasks[id] = task
93+
}
94+
}
95+
96+
func stopBackgroundMonitoring() {
97+
let id = ObjectIdentifier(self)
98+
Self.monitoringTasksQueue.sync {
99+
guard let task = Self.monitoringTasks.removeValue(forKey: id) else { return }
100+
task.cancel()
101+
}
102+
}
103+
104+
static func cancelAllMonitoring() {
105+
Self.monitoringTasksQueue.sync {
106+
for (_, task) in Self.monitoringTasks { task.cancel() }
107+
Self.monitoringTasks.removeAll()
119108
}
120109
}
121110
}

BDKSwiftExampleWallet/Service/BDK Service/BDKService.swift

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,7 @@ private class BDKService {
100100
let storedClientType = try? keyClient.getClientType()
101101
self.clientType = storedClientType ?? .esplora
102102

103-
// If starting in Kyoto, constrain in-memory network to Signet, but do not persist here.
104-
// Persistence should happen only when the user confirms Kyoto via updateClientType/updateNetwork.
105-
if self.clientType == .kyoto && self.network != .signet {
106-
self.network = .signet
107-
}
103+
// No init-time coercion; backend selection handles constraints
108104

109105
if self.clientType == .kyoto {
110106
self.blockchainURL = Constants.Config.Kyoto.getDefaultPeer(for: self.network)
@@ -124,47 +120,35 @@ private class BDKService {
124120
return
125121
}
126122

127-
// If Kyoto is selected force network to Signet and persist correction
128-
if self.clientType == .kyoto && newNetwork != .signet {
129-
self.network = .signet
130-
try? keyClient.saveNetwork(Network.signet.description)
131-
self.blockchainURL = Constants.Config.Kyoto.getDefaultPeer(for: .signet)
132-
updateBlockchainClient()
133-
return
134-
}
135-
136123
self.network = newNetwork
137124
try? keyClient.saveNetwork(newNetwork.description)
138125

139126
// Only update URL for Esplora clients, Kyoto uses peer addresses
140127
if self.clientType == .esplora {
141128
let newURL = newNetwork.url
142129
updateBlockchainURL(newURL)
143-
} else if self.clientType == .kyoto {
144-
// For Kyoto update to the correct peer for the new network
145-
let newPeer = Constants.Config.Kyoto.getDefaultPeer(for: newNetwork)
146-
self.blockchainURL = newPeer
147-
updateBlockchainClient()
148130
}
149131
}
150132
}
151133

152134
func updateBlockchainURL(_ newURL: String) {
153-
if newURL != self.blockchainURL {
154-
self.blockchainURL = newURL
155-
try? keyClient.saveEsploraURL(newURL)
156-
updateBlockchainClient()
157-
}
135+
if newURL == self.blockchainURL { return }
136+
self.blockchainURL = newURL
137+
try? keyClient.saveEsploraURL(newURL)
138+
updateBlockchainClient()
158139
}
159140

160141
internal func updateBlockchainClient() {
161142
do {
162143
switch clientType {
163144
case .esplora:
145+
// Cancel any Kyoto background tasks when switching to Esplora
146+
CbfClient.cancelAllMonitoring()
164147
self.blockchainClient = .esplora(url: self.blockchainURL)
165148
case .kyoto:
166149
if self.network != .signet {
167150
self.clientType = .esplora
151+
CbfClient.cancelAllMonitoring()
168152
self.blockchainClient = .esplora(url: self.blockchainURL)
169153
} else {
170154
let peer =
@@ -178,6 +162,7 @@ private class BDKService {
178162
}
179163
} catch {
180164
self.clientType = .esplora
165+
CbfClient.cancelAllMonitoring()
181166
self.blockchainClient = .esplora(url: self.blockchainURL)
182167
}
183168
}

BDKSwiftExampleWallet/View Model/WalletViewModel.swift

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,17 @@ class WalletViewModel {
9595
object: nil,
9696
queue: .main
9797
) { [weak self] notification in
98+
guard let self else { return }
99+
// Ignore Kyoto updates unless client type is Kyoto
100+
if self.bdkClient.getClientType() != .kyoto { return }
98101
if let progress = notification.userInfo?["progress"] as? Float {
99-
self?.updateKyotoProgress(progress)
102+
self.updateKyotoProgress(progress)
100103

101104
// Update sync state based on Kyoto progress
102105
if progress >= 100 {
103-
self?.walletSyncState = .synced
106+
self.walletSyncState = .synced
104107
} else if progress > 0 {
105-
self?.walletSyncState = .syncing
108+
self.walletSyncState = .syncing
106109
}
107110
}
108111
}
@@ -132,13 +135,16 @@ class WalletViewModel {
132135
object: nil,
133136
queue: .main
134137
) { [weak self] notification in
138+
guard let self else { return }
139+
// Ignore Kyoto updates unless client type is Kyoto
140+
if self.bdkClient.getClientType() != .kyoto { return }
135141
if let height = notification.userInfo?["height"] as? UInt32 {
136-
self?.currentBlockHeight = height
142+
self.currentBlockHeight = height
137143
// Auto-refresh wallet data when Kyoto receives new blocks
138-
self?.getBalance()
139-
self?.getTransactions()
144+
self.getBalance()
145+
self.getTransactions()
140146
Task {
141-
await self?.getPrices()
147+
await self.getPrices()
142148
}
143149
}
144150
}

0 commit comments

Comments
 (0)