Skip to content

Commit 0ec14f1

Browse files
committed
fix: persist channel locally in case remote backup fails
1 parent 960d8f5 commit 0ec14f1

File tree

3 files changed

+83
-25
lines changed

3 files changed

+83
-25
lines changed

lib/android/src/main/java/com/reactnativeldk/classes/LdkPersister.kt

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,37 +22,65 @@ class LdkPersister {
2222
val file = File(LdkModule.channelStoragePath + "/" + channelId + ".bin")
2323

2424
val isNew = !file.exists()
25+
val serialized = data.write()
2526

27+
// If we're not remotely backing up, write locally and return
2628
if (BackupClient.skipRemoteBackup) {
27-
file.writeBytes(data.write())
29+
file.writeBytes(serialized)
2830
if (isNew) {
2931
LdkEventEmitter.send(EventTypes.new_channel, body)
3032
}
3133
return ChannelMonitorUpdateStatus.LDKChannelMonitorUpdateStatus_Completed
3234
}
3335

34-
BackupClient.addToPersistQueue(BackupClient.Label.CHANNEL_MONITOR(channelId=channelId), data.write()) { error ->
36+
// For new channels: write locally first to prevent loss if app is killed during backup
37+
// Then try remote backup asynchronously
38+
if (isNew) {
39+
file.writeBytes(serialized)
40+
41+
// Update chain monitor on main thread
42+
LdkModule.reactContext?.runOnUiThread {
43+
val res = LdkModule.chainMonitor?.channel_monitor_updated(channelFundingOutpoint, data._latest_update_id)
44+
if (res == null || !res.is_ok) {
45+
LdkEventEmitter.send(EventTypes.native_log, "Failed to update chain monitor with persisted channel (${channelId})")
46+
} else {
47+
LdkEventEmitter.send(EventTypes.native_log, "Persisted channel (${channelId}) to disk")
48+
LdkEventEmitter.send(EventTypes.new_channel, body)
49+
}
50+
}
51+
52+
// Kick off remote backup asynchronously (non-blocking)
53+
BackupClient.addToPersistQueue(BackupClient.Label.CHANNEL_MONITOR(channelId=channelId), serialized) { error ->
54+
if (error != null) {
55+
LdkEventEmitter.send(EventTypes.native_log, "Warning. Remote backup failed for new channel (${channelId}), but channel was persisted locally. $error")
56+
}
57+
}
58+
59+
return ChannelMonitorUpdateStatus.LDKChannelMonitorUpdateStatus_Completed
60+
}
61+
62+
// For updates: try remote backup first, then write locally on success (original behavior)
63+
BackupClient.addToPersistQueue(BackupClient.Label.CHANNEL_MONITOR(channelId=channelId), serialized) { error ->
3564
if (error != null) {
3665
LdkEventEmitter.send(EventTypes.native_log, "Failed to persist channel (${channelId}) to remote backup: $error")
3766
return@addToPersistQueue
3867
}
3968

4069
try {
41-
file.writeBytes(data.write())
70+
file.writeBytes(serialized)
4271
} catch (e: Exception) {
4372
//If this fails we can't do much but LDK will retry on startup
4473
LdkEventEmitter.send(EventTypes.native_log, "Failed to locally persist channel (${channelId}) to disk")
4574
return@addToPersistQueue
4675
}
4776

48-
//Update chain monitor with successful persist
49-
val res = LdkModule.chainMonitor?.channel_monitor_updated(channelFundingOutpoint, data._latest_update_id)
50-
if (res == null || !res.is_ok) {
51-
LdkEventEmitter.send(EventTypes.native_log, "Failed to update chain monitor with persisted channel (${channelId})")
52-
} else {
53-
LdkEventEmitter.send(EventTypes.native_log, "Persisted channel (${channelId}) to disk")
54-
if (isNew) {
55-
LdkEventEmitter.send(EventTypes.new_channel, body)
77+
//Update chain monitor with successful persist on main thread
78+
LdkModule.reactContext?.runOnUiThread {
79+
val res = LdkModule.chainMonitor?.channel_monitor_updated(channelFundingOutpoint, data._latest_update_id)
80+
if (res == null || !res.is_ok) {
81+
LdkEventEmitter.send(EventTypes.native_log, "Failed to update chain monitor with persisted channel (${channelId})")
82+
} else {
83+
LdkEventEmitter.send(EventTypes.native_log, "Persisted channel (${channelId}) to disk")
5684
}
5785
}
5886
}

lib/ios/Classes/LdkPersist.swift

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,39 +27,69 @@ class LdkPersister: Persist {
2727
}
2828

2929
let isNew = !FileManager().fileExists(atPath: channelStoragePath.path)
30+
let serialized = Data(data.write())
3031

3132
// If we're not remotely backing up no need to update status later
3233
if BackupClient.skipRemoteBackup {
33-
try Data(data.write()).write(to: channelStoragePath)
34+
try serialized.write(to: channelStoragePath)
3435
if isNew {
3536
LdkEventEmitter.shared.send(withEvent: .new_channel, body: body)
3637
}
3738
return ChannelMonitorUpdateStatus.Completed
3839
}
39-
40-
BackupClient.addToPersistQueue(.channelMonitor(id: channelIdHex), data.write()) { error in
40+
41+
// For new channels: write locally first to prevent loss if app is killed during backup
42+
// Then try remote backup asynchronously
43+
if isNew {
44+
try serialized.write(to: channelStoragePath)
45+
46+
// Notify chain monitor on main thread
47+
DispatchQueue.main.async {
48+
let res = Ldk.chainMonitor?.channelMonitorUpdated(
49+
fundingTxo: channelFundingOutpoint,
50+
completedUpdateId: data.getLatestUpdateId()
51+
)
52+
if let error = res?.getError() {
53+
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Error. Failed to update chain monitor for channel (\(channelIdHex)) Error \(error.getValueType()).")
54+
} else {
55+
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Persisted channel \(channelIdHex). Update ID: \(data.getLatestUpdateId())")
56+
LdkEventEmitter.shared.send(withEvent: .new_channel, body: body)
57+
}
58+
}
59+
60+
// Kick off remote backup asynchronously (non-blocking)
61+
BackupClient.addToPersistQueue(.channelMonitor(id: channelIdHex), [UInt8](serialized)) { error in
62+
if let error {
63+
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Warning. Remote backup failed for new channel (\(channelIdHex)), but channel was persisted locally. \(error.localizedDescription)")
64+
}
65+
}
66+
67+
return ChannelMonitorUpdateStatus.Completed
68+
}
69+
70+
// For updates: try remote backup first, then write locally on success (original behavior)
71+
BackupClient.addToPersistQueue(.channelMonitor(id: channelIdHex), [UInt8](serialized)) { error in
4172
if let error {
4273
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Error. Failed persist channel on remote server (\(channelIdHex)). \(error.localizedDescription)")
4374
return
4475
}
4576

46-
// Callback for when the persist queue queue entry is processed
77+
// Callback for when the persist queue entry is processed
4778
do {
48-
try Data(data.write()).write(to: channelStoragePath)
79+
try serialized.write(to: channelStoragePath)
4980
} catch {
5081
// If this fails we can't do much but LDK will retry on startup
5182
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Error. Failed to locally persist channel (\(channelIdHex)). \(error.localizedDescription)")
5283
return
5384
}
5485

55-
// Update chainmonitor with successful persist
56-
let res = Ldk.chainMonitor?.channelMonitorUpdated(fundingTxo: channelFundingOutpoint, completedUpdateId: data.getLatestUpdateId())
57-
if let error = res?.getError() {
58-
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Error. Failed to update chain monitor for channel (\(channelIdHex)) Error \(error.getValueType()).")
59-
} else {
60-
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Persisted channel \(channelIdHex). Update ID: \(data.getLatestUpdateId())")
61-
if isNew {
62-
LdkEventEmitter.shared.send(withEvent: .new_channel, body: body)
86+
// Update chainmonitor with successful persist on main thread
87+
DispatchQueue.main.async {
88+
let res = Ldk.chainMonitor?.channelMonitorUpdated(fundingTxo: channelFundingOutpoint, completedUpdateId: data.getLatestUpdateId())
89+
if let error = res?.getError() {
90+
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Error. Failed to update chain monitor for channel (\(channelIdHex)) Error \(error.getValueType()).")
91+
} else {
92+
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Persisted channel \(channelIdHex). Update ID: \(data.getLatestUpdateId())")
6393
}
6494
}
6595
}

lib/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@synonymdev/react-native-ldk",
33
"title": "React Native LDK",
4-
"version": "0.0.160",
4+
"version": "0.0.161",
55
"description": "React Native wrapper for LDK",
66
"main": "./dist/index.js",
77
"types": "./dist/index.d.ts",

0 commit comments

Comments
 (0)