Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 3 additions & 3 deletions Bitkit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,7 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/synonymdev/vss-rust-client-ffi";
requirement = {
branch = "master";
branch = master;
kind = branch;
};
};
Expand Down Expand Up @@ -925,8 +925,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/synonymdev/ldk-node";
requirement = {
branch = main;
kind = branch;
kind = revision;
revision = d2a82a2d111e5eb84a0eec02f4754e39fea4189a;
};
};
96DEA0382DE8BBA1009932BF /* XCRemoteSwiftPackageReference "bitkit-core" */ = {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Bitkit/Constants/Env.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ enum Env {
return [
.init(nodeId: "039b8b4dd1d88c2c5db374290cda397a8f5d79f312d6ea5d5bfdfc7c6ff363eae3", host: "34.65.111.104", port: 9735),
.init(nodeId: "03816141f1dce7782ec32b66a300783b1d436b19777e7c686ed00115bd4b88ff4b", host: "34.65.191.64", port: 9735),
.init(nodeId: "02a371038863605300d0b3fc9de0cf5ccb57728b7f8906535709a831b16e311187", host: "34.65.186.40", port: 9735),
.init(nodeId: "02a371038863605300d0b3fc9de0cf5ccb57728b7f8906535709a831b16e311187", host: "34.65.153.174", port: 9735),
]
case .signet:
return []
Expand Down
45 changes: 43 additions & 2 deletions Bitkit/Services/LightningService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -306,20 +306,61 @@
Logger.info("Deleted network graph cache at: \(graphPath.path)")
}

func connectToTrustedPeers() async throws {
func connectToTrustedPeers(remotePeers: [LnPeer]? = nil) async throws {
guard let node else {
throw AppError(serviceError: .nodeNotSetup)
}

let peers: [LnPeer]
let usingRemotePeers: Bool
if let remotePeers, !remotePeers.isEmpty {
Logger.info("Using \(remotePeers.count) trusted peers from Blocktank API")
peers = remotePeers
usingRemotePeers = true
} else {
Logger.warn("No remote peers available, falling back to preconfigured env peers")
peers = Env.trustedLnPeers
usingRemotePeers = false
}

try await ServiceQueue.background(.ldk) {
for peer in Env.trustedLnPeers {
for peer in peers {
do {
try node.connect(nodeId: peer.nodeId, address: peer.address, persist: true)
Logger.info("Connected to trusted peer: \(peer.nodeId)")
} catch {
Logger.error(error, context: "Peer: \(peer.nodeId)")
}
}

if usingRemotePeers {
self.verifyTrustedPeersOrFallback(node: node, trustedPeers: peers)
}
}
}

private func verifyTrustedPeersOrFallback(node: Node, trustedPeers: [LnPeer]) {
let connectedPeerIds = Set(node.listPeers().filter(\.isConnected).map(\.nodeId))
let trustedConnected = trustedPeers.filter { connectedPeerIds.contains($0.nodeId) }.count
let trustedPeerIds = Set(trustedPeers.map(\.nodeId))

if trustedConnected == 0, !trustedPeers.isEmpty {
let fallbackPeers = Env.trustedLnPeers.filter { !trustedPeerIds.contains($0.nodeId) }
if fallbackPeers.isEmpty {
Logger.warn("No trusted peers connected. All preconfigured env peers overlap with API peers (not retrying with stale addresses).")
} else {
Logger.warn("No trusted peers connected, trying \(fallbackPeers.count) preconfigured env peer(s) not in API list")
for peer in fallbackPeers {
do {
try node.connect(nodeId: peer.nodeId, address: peer.address, persist: true)
Logger.info("Connected to fallback peer: \(peer.nodeId)")
} catch {
Logger.error(error, context: "Fallback peer: \(peer.nodeId)")
}
}
}
} else {
Logger.info("Connected to \(trustedConnected)/\(trustedPeers.count) trusted peers")
}
}

Expand Down Expand Up @@ -541,7 +582,7 @@
}

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

Check warning on line 585 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

Check warning on line 585 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

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

View workflow job for this annotation

GitHub Actions / Run Integration Tests

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

Expand Down Expand Up @@ -916,7 +957,7 @@
onEvent?(event)

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

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

View workflow job for this annotation

GitHub Actions / Run Integration 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 @@ -941,7 +982,7 @@
Logger.warn("No paymentId or paymentHash available for failed payment", context: "LightningService")
}
}
case let .paymentReceived(paymentId, paymentHash, amountMsat, feePaidMsat):

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

View workflow job for this annotation

GitHub Actions / Run Integration 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 @@ -951,7 +992,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 995 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

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

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

View workflow job for this annotation

GitHub Actions / Run Integration 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 @@ -980,7 +1021,7 @@

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

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

View workflow job for this annotation

GitHub Actions / Run Tests

result of call to 'run(resultType:body:)' is unused

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

View workflow job for this annotation

GitHub Actions / Run Tests

result of call to 'run(resultType:body:)' is unused

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

View workflow job for this annotation

GitHub Actions / Run Integration Tests

result of call to 'run(resultType:body:)' is unused
channelCache.removeValue(forKey: channelIdString)
}
} else {
Expand All @@ -1003,7 +1044,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 1047 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

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

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

View workflow job for this annotation

GitHub Actions / Run Integration 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 @@ -1057,7 +1098,7 @@

// MARK: Balance Events

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

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

View workflow job for this annotation

GitHub Actions / Run Integration Tests

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

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

View workflow job for this annotation

GitHub Actions / Run Integration 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
25 changes: 24 additions & 1 deletion Bitkit/ViewModels/WalletViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ class WalletViewModel: ObservableObject {
syncState()

do {
try await lightningService.connectToTrustedPeers()
let remotePeers = await fetchTrustedPeersFromBlocktank()
try await lightningService.connectToTrustedPeers(remotePeers: remotePeers)
} catch {
Logger.error("Failed to connect to trusted peers")
}
Expand All @@ -232,6 +233,28 @@ class WalletViewModel: ObservableObject {
}
}

private func fetchTrustedPeersFromBlocktank() async -> [LnPeer]? {
var info: IBtInfo?
do {
info = try await coreService.blocktank.info(refresh: true)
} catch {
Logger.warn("Blocktank API refresh failed, trying cache: \(error)")
}
if info == nil {
info = try? await coreService.blocktank.info(refresh: false)
}
guard let nodes = info?.nodes, !nodes.isEmpty else { return nil }
let peers = nodes.compactMap { node -> LnPeer? in
guard let connString = node.connectionStrings.first else { return nil }
let address = connString.contains("@") ? String(connString.split(separator: "@").last ?? "") : connString
let parts = address.split(separator: ":")
guard parts.count == 2, let port = UInt16(parts[1]) else { return nil }
return LnPeer(nodeId: node.pubkey, host: String(parts[0]), port: port)
}
Logger.info("Fetched \(peers.count) trusted peers from Blocktank API")
return peers.isEmpty ? nil : peers
}

func stopLightningNode(clearEventCallback: Bool = false) async throws {
nodeLifecycleState = .stopping
try await lightningService.stop(clearEventCallback: clearEventCallback)
Expand Down
Loading