From 6661c82fd2258d2b68d98efd2610b35153265b9d Mon Sep 17 00:00:00 2001 From: benk10 Date: Wed, 12 Aug 2026 13:14:46 -0500 Subject: [PATCH 1/5] fix: stop auto-accepting stale monitors Co-authored-by: Cursor --- Bitkit/Services/LightningService.swift | 51 +++++----------------- changelog.d/next/stale-monitor.security.md | 1 + 2 files changed, 13 insertions(+), 39 deletions(-) create mode 100644 changelog.d/next/stale-monitor.security.md diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index 86d0070cc..887999f28 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -154,46 +154,19 @@ class LightningService { builder.setEntropyBip39Mnemonic(mnemonic: mnemonic, passphrase: passphrase) try await ServiceQueue.background(.ldk) { - 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" + 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: [:] ) - 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/changelog.d/next/stale-monitor.security.md b/changelog.d/next/stale-monitor.security.md new file mode 100644 index 000000000..3cf6e0c29 --- /dev/null +++ b/changelog.d/next/stale-monitor.security.md @@ -0,0 +1 @@ +Lightning no longer automatically starts from outdated channel monitor data after a storage mismatch. From 51189e79441a03441351a8d9726114f00b87b03a Mon Sep 17 00:00:00 2001 From: benk10 Date: Wed, 12 Aug 2026 13:15:15 -0500 Subject: [PATCH 2/5] chore: rename changelog fragment Co-authored-by: Cursor --- changelog.d/next/{stale-monitor.security.md => 664.security.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/next/{stale-monitor.security.md => 664.security.md} (100%) diff --git a/changelog.d/next/stale-monitor.security.md b/changelog.d/next/664.security.md similarity index 100% rename from changelog.d/next/stale-monitor.security.md rename to changelog.d/next/664.security.md From 108889ff20ae4c97c65eed2d9dd42e6a82f6a4a4 Mon Sep 17 00:00:00 2001 From: benk10 Date: Wed, 12 Aug 2026 13:26:32 -0500 Subject: [PATCH 3/5] fix: harden stale monitor rejection Co-authored-by: Cursor --- Bitkit/Services/LightningService.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index 887999f28..23961cb70 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -75,6 +75,12 @@ class LightningService { Logger.debug("Checking lightning process lock...") try await StateLocker.lock(.lightning, wait: 30) // Wait 30 seconds to lock because maybe extension is still running + var shouldReleaseLightningLock = true + defer { + if shouldReleaseLightningLock { + try? StateLocker.unlock(.lightning) + } + } guard var mnemonic = try Keychain.loadString(key: .bip39Mnemonic(index: walletIndex)) else { throw CustomServiceError.mnemonicNotFound @@ -112,6 +118,7 @@ class LightningService { let builder = Builder.fromConfig(config: config) builder.setCustomLogger(logWriter: LdkLogWriter()) + builder.setAcceptStaleChannelMonitors(accept: false) let resolvedElectrumServerUrl = electrumServerUrl ?? Env.electrumServerUrl @@ -169,6 +176,7 @@ class LightningService { ) } } + shouldReleaseLightningLock = false Logger.info("LDK node setup") From c2728bfaf2daa1a6353f8b9e1f9193127ab46441 Mon Sep 17 00:00:00 2001 From: benk10 Date: Wed, 12 Aug 2026 13:35:19 -0500 Subject: [PATCH 4/5] fix: drop stale monitor builder flag Co-authored-by: Cursor --- Bitkit/Services/LightningService.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index 23961cb70..dedcd3e20 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -118,7 +118,6 @@ class LightningService { let builder = Builder.fromConfig(config: config) builder.setCustomLogger(logWriter: LdkLogWriter()) - builder.setAcceptStaleChannelMonitors(accept: false) let resolvedElectrumServerUrl = electrumServerUrl ?? Env.electrumServerUrl From a16709d4dfdcd4882d373156c4734166055c3b44 Mon Sep 17 00:00:00 2001 From: benk10 Date: Thu, 13 Aug 2026 09:01:55 -0500 Subject: [PATCH 5/5] docs: explain lightning lock release Co-authored-by: Cursor --- Bitkit/Services/LightningService.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index dedcd3e20..16cb2a0ae 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -75,6 +75,7 @@ class LightningService { Logger.debug("Checking lightning process lock...") try await StateLocker.lock(.lightning, wait: 30) // Wait 30 seconds to lock because maybe extension is still running + // Unlock if setup fails before a node exists. After a successful build the node keeps the lock until stop. var shouldReleaseLightningLock = true defer { if shouldReleaseLightningLock {