diff --git a/Bitkit.xcodeproj/project.pbxproj b/Bitkit.xcodeproj/project.pbxproj index 3b930521..d6e4a2ee 100644 --- a/Bitkit.xcodeproj/project.pbxproj +++ b/Bitkit.xcodeproj/project.pbxproj @@ -928,7 +928,7 @@ repositoryURL = "https://github.com/synonymdev/ldk-node"; requirement = { kind = revision; - revision = c5698d00066e0e50f33696afc562d71023da2373; + revision = ae38eadab70fceb5dbe242bc02bf895581cb7c3f; }; }; 96DEA0382DE8BBA1009932BF /* XCRemoteSwiftPackageReference "bitkit-core" */ = { diff --git a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e14c1eae..b1ca3574 100644 --- a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,7 +24,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/synonymdev/ldk-node", "state" : { - "revision" : "c5698d00066e0e50f33696afc562d71023da2373" + "revision" : "ae38eadab70fceb5dbe242bc02bf895581cb7c3f" } }, { diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index 2bfa4a27..357287e2 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -95,7 +95,8 @@ class LightningService { onchainWalletSyncIntervalSecs: Env.walletSyncIntervalSecs, lightningWalletSyncIntervalSecs: Env.walletSyncIntervalSecs, feeRateCacheUpdateIntervalSecs: Env.walletSyncIntervalSecs - ) + ), + connectionTimeoutSecs: 10 ) builder.setChainSourceElectrum(serverUrl: resolvedElectrumServerUrl, config: electrumConfig) @@ -124,19 +125,46 @@ class LightningService { builder.setEntropyBip39Mnemonic(mnemonic: mnemonic, passphrase: passphrase) try await ServiceQueue.background(.ldk) { - if !lnurlAuthServerUrl.isEmpty { - self.node = try builder.buildWithVssStore( - vssUrl: vssUrl, - storeId: storeId, - lnurlAuthServerUrl: lnurlAuthServerUrl, - fixedHeaders: [:] - ) - } else { - self.node = try builder.buildWithVssStoreAndFixedHeaders( - vssUrl: vssUrl, - storeId: storeId, - fixedHeaders: [:] + do { + if !lnurlAuthServerUrl.isEmpty { + self.node = try builder.buildWithVssStore( + vssUrl: vssUrl, + storeId: storeId, + lnurlAuthServerUrl: lnurlAuthServerUrl, + fixedHeaders: [:] + ) + } else { + self.node = try builder.buildWithVssStoreAndFixedHeaders( + vssUrl: vssUrl, + storeId: storeId, + fixedHeaders: [:] + ) + } + } catch let error as BuildError { + guard case .DangerousValue = error else { throw error } + + // Stale ChannelMonitor vs ChannelManager — retry with accept_stale to recover. + Logger.warn( + "Build failed with DangerousValue. Retrying with accept_stale_channel_monitors for recovery.", + context: "Recovery" ) + builder.setAcceptStaleChannelMonitors(accept: true) + + if !lnurlAuthServerUrl.isEmpty { + self.node = try builder.buildWithVssStore( + vssUrl: vssUrl, + storeId: storeId, + lnurlAuthServerUrl: lnurlAuthServerUrl, + fixedHeaders: [:] + ) + } else { + self.node = try builder.buildWithVssStoreAndFixedHeaders( + vssUrl: vssUrl, + storeId: storeId, + fixedHeaders: [:] + ) + } + Logger.info("Stale monitor recovery: build succeeded with accept_stale", context: "Recovery") } } diff --git a/Bitkit/Utilities/Errors.swift b/Bitkit/Utilities/Errors.swift index e4b1f62d..6630577f 100644 --- a/Bitkit/Utilities/Errors.swift +++ b/Bitkit/Utilities/Errors.swift @@ -185,6 +185,9 @@ struct AppError: LocalizedError { case let .ReadFailed(message: ldkMessage): message = "Read failed" debugMessage = ldkMessage + case let .DangerousValue(message: ldkMessage): + message = "Dangerous value" + debugMessage = ldkMessage case let .WriteFailed(message: ldkMessage): message = "Write failed" debugMessage = ldkMessage diff --git a/Bitkit/ViewModels/WalletViewModel.swift b/Bitkit/ViewModels/WalletViewModel.swift index 1949c215..0678251d 100644 --- a/Bitkit/ViewModels/WalletViewModel.swift +++ b/Bitkit/ViewModels/WalletViewModel.swift @@ -138,20 +138,20 @@ class WalletViewModel: ObservableObject { } // If no local migration data, try fetching from RN remote backup (one-time) - // if channelMigration == nil { - // let (remoteMigration, allRetrieved) = await fetchOrphanedChannelMonitorsIfNeeded(walletIndex: walletIndex) - // if let remoteMigration { - // channelMigration = ChannelDataMigration( - // // don't overwrite channel manager, we only need the monitors for the sweep - // channelManager: nil, - // channelMonitors: remoteMigration.channelMonitors.map { [UInt8]($0) } - // ) - // MigrationsService.shared.pendingChannelMigration = nil - // } - // if allRetrieved { - // MigrationsService.shared.isChannelRecoveryChecked = true - // } - // } + if channelMigration == nil { + let (remoteMigration, allRetrieved) = await fetchOrphanedChannelMonitorsIfNeeded(walletIndex: walletIndex) + if let remoteMigration { + channelMigration = ChannelDataMigration( + // don't overwrite channel manager, we only need the monitors for the sweep + channelManager: nil, + channelMonitors: remoteMigration.channelMonitors.map { [UInt8]($0) } + ) + MigrationsService.shared.pendingChannelMigration = nil + } + if allRetrieved { + MigrationsService.shared.isChannelRecoveryChecked = true + } + } await runLegacyNetworkGraphCleanupIfNeeded()