Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ios restart debounce, dropped peer connection handler #249

Merged
merged 1 commit into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
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
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ PODS:
- React-jsinspector (0.72.4)
- React-logger (0.72.4):
- glog
- react-native-ldk (0.0.141):
- react-native-ldk (0.0.143):
- React
- react-native-randombytes (3.6.1):
- React-Core
Expand Down Expand Up @@ -621,7 +621,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: c7f826e40fa9cab5d37cab6130b1af237332b594
React-jsinspector: aaed4cf551c4a1c98092436518c2d267b13a673f
React-logger: da1ebe05ae06eb6db4b162202faeafac4b435e77
react-native-ldk: 58b28973bedc64333c350b9074554f814ba3daec
react-native-ldk: 12d78fe1141ad4343a2842340f7ebf8539dcc3b0
react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846
react-native-tcp-socket: c1b7297619616b4c9caae6889bcb0aba78086989
React-NativeModulesApple: edb5ace14f73f4969df6e7b1f3e41bef0012740f
Expand Down
2 changes: 1 addition & 1 deletion lib/android/src/main/java/com/reactnativeldk/LdkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
peerHandler!!.connect(pubKey.hexa(), InetSocketAddress(address, port.toInt()), 3000)
LdkEventEmitter.send(EventTypes.native_log, "Connection to peer $pubKey re-established by handleDroppedPeers().")
} catch (e: Exception) {
LdkEventEmitter.send(EventTypes.native_log, "Error connecting peer $pubKey. Error: $e")
LdkEventEmitter.send(EventTypes.native_log, "Error connecting peer from handleDroppedPeers() $pubKey. Error: $e")
} finally {
currentlyConnectingPeers.remove(pubKey)
}
Expand Down
145 changes: 137 additions & 8 deletions lib/ios/Ldk.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ enum LdkCallbackResponses: String {
case config_init_success = "config_init_success"
case network_graph_init_success = "network_graph_init_success"
case add_peer_success = "add_peer_success"
case add_peer_skipped = "add_peer_skipped"
case peer_already_connected = "peer_already_connected"
case peer_currently_connecting = "peer_currently_connecting"
case chain_sync_success = "chain_sync_success"
case invoice_payment_success = "invoice_payment_success"
case tx_set_confirmed = "tx_set_confirmed"
Expand Down Expand Up @@ -143,6 +146,12 @@ class Ldk: NSObject {
var currentBlockchainTipHash: NSString?
var currentBlockchainHeight: NSInteger?

//Peer connection checks
var backgroundedAt: Date? = nil
var addedPeers: [(String, Int, String)] = []
var currentlyConnectingPeers: [String] = []
var droppedPeerTimer: Timer? = nil

//Static to be accessed from other classes
static var accountStoragePath: URL?
static var channelStoragePath: URL?
Expand Down Expand Up @@ -495,25 +504,45 @@ class Ldk: NSObject {
currentBlockchainTipHash = blockHash
currentBlockchainHeight = blockHeight
addForegroundObserver()

startDroppedPeerTimer()

return handleResolve(resolve, .channel_manager_init_success)
}

func addForegroundObserver() {
removeForegroundObserver()
backgroundedAt = nil
NotificationCenter.default.addObserver(self, selector: #selector(restartOnForeground), name: UIApplication.didBecomeActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(onBackground), name: UIApplication.willResignActiveNotification, object: nil)
}

func removeForegroundObserver() {
NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil)
}

/// Used by event listener so responses are not handled
@objc
func restartOnForeground() {
let secondsSinceBackgrounded = Date().timeIntervalSince(backgroundedAt ?? .distantPast)
guard secondsSinceBackgrounded > 5 else {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Skipping restart. App was only backgrounded \(Int(secondsSinceBackgrounded))s ago")
return
}

LdkEventEmitter.shared.send(withEvent: .native_log, body: "Restarting LDK on move to foreground. App was backgrounded \(Int(secondsSinceBackgrounded))s ago")

backgroundedAt = nil
restart { res in } reject: { code, message, error in }
}

@objc
func onBackground() {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "App moved to background")

backgroundedAt = Date()
}

/// Restarts channel manager constructor to get a new TCP peer handler
@objc
func restart(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
Expand Down Expand Up @@ -559,6 +588,7 @@ class Ldk: NSObject {
return handleResolve(resolve, .ldk_stop)
}

stopStartDroppedPeerTimer()
removeForegroundObserver() //LDK was intentionally stopped and we shouldn't attempt a restart
cm.interrupt()
channelManagerConstructor = nil
Expand All @@ -571,6 +601,7 @@ class Ldk: NSObject {
peerHandler = nil
ldkNetwork = nil
ldkCurrency = nil
backgroundedAt = nil

return handleResolve(resolve, .ldk_stop)
}
Expand Down Expand Up @@ -617,16 +648,114 @@ class Ldk: NSObject {
return handleResolve(resolve, .chain_sync_success)
}

func startDroppedPeerTimer() {
guard droppedPeerTimer == nil else {
return
}

DispatchQueue.main.async { [weak self] in
guard let self else { return }

LdkEventEmitter.shared.send(withEvent: .native_log, body: "Starting timer to check for dropped peers")

droppedPeerTimer = Timer.scheduledTimer(
timeInterval: 5.0,
target: self,
selector: #selector(handleDroppedPeers),
userInfo: nil,
repeats: true
)
}
}

func stopStartDroppedPeerTimer() {
droppedPeerTimer?.invalidate()
droppedPeerTimer = nil
}

@objc func handleDroppedPeers() {
guard backgroundedAt == nil else {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "App was backgrounded, skipping handleDroppedPeers()")
return
}

guard channelManagerConstructor != nil else {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "channelManagerConstructor not intialized, skipping handleDroppedPeers()")
return
}

guard let peerHandler else {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "peerHandler not intialized, skipping handleDroppedPeers()")
return
}

guard let peerManager else {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "peerManager not intialized, skipping handleDroppedPeers()")
return
}

LdkEventEmitter.shared.send(withEvent: .native_log, body: "Checking for dropped peers")

let currentList = peerManager.getPeerNodeIds().map { Data($0.0).hexEncodedString() }

addedPeers.forEach { (address, port, pubKey) in
guard !currentList.contains(pubKey) else {
return
}

currentlyConnectingPeers.append(String(pubKey))
let res = peerHandler.connect(address: String(address), port: UInt16(port), theirNodeId: String(pubKey).hexaBytes)
currentlyConnectingPeers.removeAll { $0 == String(pubKey) }

if res {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Connection to peer \(pubKey) re-established by handleDroppedPeers().")
} else {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Error connecting peer \(pubKey) from handleDroppedPeers().")
}
}
}

@objc
func addPeer(_ address: NSString, port: NSInteger, pubKey: NSString, timeout: NSInteger, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
//timeout param not used. Only for android.

//Sync ChannelMonitors and ChannelManager to chain tip
guard backgroundedAt == nil else {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "App was backgrounded, skipping addPeer()")
return handleResolve(resolve, .add_peer_skipped)
}

guard let peerHandler = peerHandler else {
return handleReject(reject, .init_peer_handler)
}

guard let peerManager = peerManager else {
return handleReject(reject, .init_peer_manager)
}

//If peer is already connected don't add again
let currentList = peerManager.getPeerNodeIds().map { Data($0.0).hexEncodedString() }
guard !currentList.contains(String(pubKey)) else {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Skipping new peer connection, already connected to \(pubKey)")
return handleResolve(resolve, .peer_already_connected)
}

guard !currentlyConnectingPeers.contains(String(pubKey)) else {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Skipping additional peer connection, already busy connecting to \(pubKey)")
return handleResolve(resolve, .peer_currently_connecting)
}

//Add to retry list if peers are dropped

currentlyConnectingPeers.append(String(pubKey))
let res = peerHandler.connect(address: String(address), port: UInt16(port), theirNodeId: String(pubKey).hexaBytes)
currentlyConnectingPeers.removeAll { $0 == String(pubKey) }

if !addedPeers.contains(where: { (_, _, pk) in
pk == String(pubKey)
}) {
addedPeers.append((String(address), Int(port), String(pubKey)))
}

if !res {
return handleReject(reject, .add_peer_fail)
}
Expand Down Expand Up @@ -846,7 +975,7 @@ class Ldk: NSObject {
guard !(amountSats > 0 && !isZeroValueInvoice) else {
return handleReject(reject, .invoice_payment_fail_must_not_specify_amount)
}

let paymentId = invoice.paymentHash()!
let (paymentHash, recipientOnion, routeParameters) = isZeroValueInvoice ? Bindings.paymentParametersFromZeroAmountInvoice(invoice: invoice, amountMsat: UInt64(amountSats*1000)).getValue()! : Bindings.paymentParametersFromInvoice(invoice: invoice).getValue()!

Expand Down Expand Up @@ -1038,7 +1167,7 @@ class Ldk: NSObject {
}

let excludeChannelIds = ignoreOpenChannels ? channelManager.listChannels().map { Data($0.getChannelId() ?? []).hexEncodedString() }.filter { $0 != "" } : []

let channelFiles = try! FileManager.default.contentsOfDirectory(at: channelStoragePath, includingPropertiesForKeys: nil)

var result: [[String: Any?]] = []
Expand Down Expand Up @@ -1261,9 +1390,9 @@ class Ldk: NSObject {
}

let openChannelIds = channelManager.listChannels().map { Data($0.getChannelId() ?? []).hexEncodedString() }.filter { $0 != "" }

let channelFiles = try! FileManager.default.contentsOfDirectory(at: channelStoragePath, includingPropertiesForKeys: nil)

var txs: [String] = []

for channelFile in channelFiles {
Expand Down Expand Up @@ -1296,7 +1425,7 @@ class Ldk: NSObject {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "No spendable outputs found in \(channelId)")
continue
}

let res = keysManager.spendSpendableOutputs(
descriptors: descriptors,
outputs: [],
Expand All @@ -1309,7 +1438,7 @@ class Ldk: NSObject {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Failed to spend output from closed channel: \(channelId).")
continue
}

txs.append(Data(res.getValue()!).hexEncodedString())
}

Expand Down
Loading