Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 60 additions & 12 deletions Bitkit/Services/LightningService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@
private var node: Node?
var currentWalletIndex: Int = 0

// MARK: - Stale monitor recovery (one-time recovery for channel monitor desync)

private static let staleMonitorRecoveryAttemptedKey = "staleMonitorRecoveryAttempted"

/// Whether we've already attempted stale monitor recovery (prevents infinite retry).
/// Persisted so the retry only happens once, even across app restarts.
private static var staleMonitorRecoveryAttempted: Bool {
get { UserDefaults.standard.bool(forKey: staleMonitorRecoveryAttemptedKey) }
set { UserDefaults.standard.set(newValue, forKey: staleMonitorRecoveryAttemptedKey) }
}

private let syncStatusChangedSubject = PassthroughSubject<UInt64, Never>()

private var channelCache: [String: ChannelDetails] = [:]
Expand Down Expand Up @@ -124,22 +135,59 @@
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 .ReadFailed = error, !Self.staleMonitorRecoveryAttempted else {
throw error
}

// Build failed with ReadFailed — likely a stale ChannelMonitor (DangerousValue).
Copy link
Member Author

Choose a reason for hiding this comment

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

Replace with specific exception

// Retry once with accept_stale_channel_monitors to recover.
Logger.warn(
"Build failed with ReadFailed. Retrying with accept_stale_channel_monitors for one-time recovery.",
context: "Recovery"
)
Self.staleMonitorRecoveryAttempted = true
builder.setAcceptStaleChannelMonitors(accept: true)

Check failure on line 165 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

value of type 'Builder' has no member 'setAcceptStaleChannelMonitors'
Copy link

Choose a reason for hiding this comment

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

Missing dependency: setAcceptStaleChannelMonitors not in pinned ldk-node revision

This call references builder.setAcceptStaleChannelMonitors(accept: true), but the method does not exist in the currently pinned synonymdev/ldk-node revision (c5698d0, set in Bitkit.xcodeproj). As a result, this branch will not compile until the project.pbxproj is updated to a commit that includes synonymdev/ldk-node#76 (which the PR description already calls out as a requirement).

)
Self.staleMonitorRecoveryAttempted = true
builder.setAcceptStaleChannelMonitors(accept: true)
if !lnurlAuthServerUrl.isEmpty {


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")
}
}

// Mark recovery as attempted after any successful build (whether recovery was needed or not).
// This ensures unaffected users never trigger the retry path on future startups.
if !Self.staleMonitorRecoveryAttempted {
Self.staleMonitorRecoveryAttempted = true
}

Logger.info("LDK node setup")

// Clear memory
Expand Down Expand Up @@ -595,7 +643,7 @@
}

func closeChannel(_ channel: ChannelDetails, force: Bool = false, forceCloseReason: String? = nil) async throws {
guard let node else {

Check warning on line 646 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

value 'node' was defined but never used; consider replacing with boolean test
throw AppError(serviceError: .nodeNotStarted)
}

Expand Down Expand Up @@ -1068,7 +1116,7 @@
onEvent?(event)

switch event {
case let .paymentSuccessful(paymentId, paymentHash, paymentPreimage, feePaidMsat):

Check warning on line 1119 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'paymentPreimage' was never used; consider replacing with '_' or removing it
Logger.info("✅ Payment successful: paymentId: \(paymentId ?? "?") paymentHash: \(paymentHash) feePaidMsat: \(feePaidMsat ?? 0)")
Task {
let hash = paymentId ?? paymentHash
Expand All @@ -1093,7 +1141,7 @@
Logger.warn("No paymentId or paymentHash available for failed payment", context: "LightningService")
}
}
case let .paymentReceived(paymentId, paymentHash, amountMsat, feePaidMsat):

Check warning on line 1144 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'feePaidMsat' was never used; consider replacing with '_' or removing it
Logger.info("🤑 Payment received: paymentId: \(paymentId ?? "?") paymentHash: \(paymentHash) amountMsat: \(amountMsat)")
Task {
let hash = paymentId ?? paymentHash
Expand All @@ -1103,7 +1151,7 @@
Logger.error("Failed to handle payment received for \(hash): \(error)", context: "LightningService")
}
}
case let .paymentClaimable(paymentId, paymentHash, claimableAmountMsat, claimDeadline, customRecords):

Check warning on line 1154 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'customRecords' was never used; consider replacing with '_' or removing it

Check warning on line 1154 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'claimDeadline' was never used; consider replacing with '_' or removing it
Logger.info(
"🫰 Payment claimable: paymentId: \(paymentId) paymentHash: \(paymentHash) claimableAmountMsat: \(claimableAmountMsat)"
)
Expand Down Expand Up @@ -1132,7 +1180,7 @@

if let channel {
await registerClosedChannel(channel: channel, reason: reasonString)
await MainActor.run {

Check warning on line 1183 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

result of call to 'run(resultType:body:)' is unused
channelCache.removeValue(forKey: channelIdString)
}
} else {
Expand All @@ -1155,7 +1203,7 @@
Logger.error("Failed to handle transaction received for \(txid): \(error)", context: "LightningService")
}
}
case let .onchainTransactionConfirmed(txid, blockHash, blockHeight, confirmationTime, details):

Check warning on line 1206 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'confirmationTime' was never used; consider replacing with '_' or removing it

Check warning on line 1206 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'blockHash' was never used; consider replacing with '_' or removing it
Logger.info("✅ Onchain transaction confirmed: txid=\(txid) blockHeight=\(blockHeight) amountSats=\(details.amountSats)")
Task {
do {
Expand Down Expand Up @@ -1209,7 +1257,7 @@

// MARK: Balance Events

case let .balanceChanged(oldSpendableOnchain, newSpendableOnchain, oldTotalOnchain, newTotalOnchain, oldLightning, newLightning):

Check warning on line 1260 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'newTotalOnchain' was never used; consider replacing with '_' or removing it

Check warning on line 1260 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'oldTotalOnchain' was never used; consider replacing with '_' or removing it
Logger
.info("💰 Balance changed: onchain=\(oldSpendableOnchain)->\(newSpendableOnchain) lightning=\(oldLightning)->\(newLightning)")

Expand Down
Loading