Skip to content

Commit

Permalink
feat: ios backup client state event
Browse files Browse the repository at this point in the history
Jasonvdb committed Jan 30, 2024
1 parent 9e28cca commit 668ddaf
Showing 4 changed files with 131 additions and 4 deletions.
41 changes: 41 additions & 0 deletions example/Dev.tsx
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ import ldk from '@synonymdev/react-native-ldk/dist/ldk';
import lm, {
EEventTypes,
ENetworks,
TBackupStateUpdate,
TChannelManagerClaim,
TChannelManagerPaymentPathFailed,
TChannelManagerPaymentPathSuccessful,
@@ -32,9 +33,11 @@ let paymentSubscription: EmitterSubscription | undefined;
let onChannelSubscription: EmitterSubscription | undefined;
let paymentFailedSubscription: EmitterSubscription | undefined;
let paymentPathSuccess: EmitterSubscription | undefined;
let backupStateUpdate: EmitterSubscription | undefined;

const Dev = (): ReactElement => {
const [message, setMessage] = useState('...');
const [backupState, setBackupState] = useState('');
const [nodeStarted, setNodeStarted] = useState(false);
const [showLogs, setShowLogs] = useState(false);
const [logContent, setLogContent] = useState('');
@@ -131,6 +134,37 @@ const Dev = (): ReactElement => {
);
}

if (!backupStateUpdate) {
// @ts-ignore
backupStateUpdate = ldk.onEvent(
EEventTypes.backup_state_update,
(res: TBackupStateUpdate) => {
const formatTime = (time: number | undefined): string | null =>
time ? new Date(time).toLocaleTimeString() : null;

let backupMessage = '';
const keys = Object.keys(res).sort();
keys.forEach((key) => {
backupMessage += `${key}:\n`;
const { lastQueued, lastPersisted, lastFailed, lastErrorMessage } =
res[key];
backupMessage += `Last Queued: ${formatTime(lastQueued)}\n`;
backupMessage += `Last Persisted: ${
formatTime(lastPersisted) ?? '⏳'
}\n`;

if (lastFailed) {
backupMessage += `Last Failed: ${formatTime(lastFailed)} ❗\n`;
backupMessage += `Last Error Message: ${lastErrorMessage} ❗\n`;
}

backupMessage += '\n\n';
});
setBackupState(backupMessage);
},
);
}

return (): void => {
logSubscription && logSubscription.remove();
paymentSubscription && paymentSubscription.remove();
@@ -149,6 +183,13 @@ const Dev = (): ReactElement => {
<View style={styles.messageContainer}>
<Text style={styles.text}>{message}</Text>
</View>

{backupState ? (
<View style={styles.messageContainer}>
<Text style={styles.text}>{backupState}</Text>
</View>
) : null}

<View style={styles.container}>
<Button
title={'E2E test'}
80 changes: 77 additions & 3 deletions lib/ios/Classes/BackupClient.swift
Original file line number Diff line number Diff line change
@@ -58,6 +58,28 @@ struct ListFilesResponse: Codable {
let channel_monitors: [String]
}

struct BackupFileState {
var lastQueued: Date
var lastPersisted: Date?
var lastFailed: Date?
var lastErrorMessage: String?

var encoded: [String: Encodable] {
[
"lastQueued": (self.lastQueued.timeIntervalSince1970 * 1000).rounded(),
"lastPersisted": self.lastPersisted != nil ? (self.lastPersisted!.timeIntervalSince1970 * 1000).rounded() : nil,
"lastFailed": self.lastFailed != nil ? (self.lastFailed!.timeIntervalSince1970 * 1000).rounded() : nil,
"lastErrorMessage": self.lastErrorMessage
]
}
}

enum BackupStateUpdateType {
case queued
case success
case fail(Error)
}

class BackupClient {
enum Label {
case ping
@@ -79,6 +101,19 @@ class BackupClient {
.replacingOccurrences(of: ".bin", with: "")
}
}

var backupStateKey: String? {
switch self {
case .channelManager:
return self.string
case .channelMonitor(let id):
return "\(self.string)_\(id)"
case .misc(let fileName):
return self.string
default:
return nil //Don't worry about the backup state event of these files
}
}
}

private enum Method: String {
@@ -115,6 +150,8 @@ class BackupClient {
static var requiresSetup: Bool {
return server == nil
}

static var backupState: [String: BackupFileState] = [:]

static func setup(secretKey: [UInt8], pubKey: [UInt8], network: String, server: String, serverPubKey: String) throws {
guard getNetwork(network) != nil else {
@@ -197,7 +234,7 @@ class BackupClient {
}
}

fileprivate static func persist(_ label: Label, _ bytes: [UInt8], retry: Int) throws {
fileprivate static func persist(_ label: Label, _ bytes: [UInt8], retry: Int, onTryFail: (Error) -> Void) throws {
var attempts: UInt32 = 0

var persistError: Error?
@@ -208,6 +245,7 @@ class BackupClient {
return
} catch {
persistError = error
onTryFail(error)
attempts += 1
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Remote persist failed for \(label.string) (\(attempts) attempts)")
sleep(attempts) //Ease off with each attempt
@@ -285,7 +323,6 @@ class BackupClient {

if let error = requestError {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Remote persist failed for \(label.string). \(error.localizedDescription)")
LdkEventEmitter.shared.send(withEvent: .backup_sync_persist_error, body: error.localizedDescription)
throw error
}

@@ -592,6 +629,36 @@ class BackupClient {

//Backup queue management
extension BackupClient {
static func updateBackupState(_ label: Label, type: BackupStateUpdateType) {
guard let key = label.backupStateKey else {
return
}

//All updates on main queue
DispatchQueue.main.async {
backupState[key] = backupState[key] ?? .init(lastQueued: Date())

switch type {
case .queued:
backupState[key]!.lastQueued = Date()
backupState[key]!.lastFailed = nil
case .success:
backupState[key]!.lastPersisted = Date()
backupState[key]!.lastFailed = nil
case .fail(let error):
backupState[key]!.lastFailed = Date()
backupState[key]!.lastErrorMessage = error.localizedDescription
}

var body: [String: [String: Encodable]] = [:]
backupState.keys.forEach { key in
body[key] = backupState[key]!.encoded
}

LdkEventEmitter.shared.send(withEvent: .backup_state_update, body: body)
}
}

static func addToPersistQueue(_ label: Label, _ bytes: [UInt8], callback: ((Error?) -> Void)? = nil) {
guard !skipRemoteBackup else {
callback?(nil)
@@ -601,6 +668,8 @@ extension BackupClient {

var backupQueue: DispatchQueue?

updateBackupState(label, type: .queued)

switch label {
case .channelManager:
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Adding channel manager backup to queue")
@@ -623,10 +692,15 @@ extension BackupClient {

backupQueue.async {
do {
try persist(label, bytes, retry: 10)
try persist(label, bytes, retry: 10) { attemptError in
//Soft fail, will keep retyring but UI can be updated in the meantime
updateBackupState(label, type: .fail(attemptError))
}
updateBackupState(label, type: .success)
callback?(nil)
} catch {
LdkEventEmitter.shared.send(withEvent: .native_log, body: "Failed to persist remote backup \(label.string). \(error.localizedDescription)")
updateBackupState(label, type: .fail(error))
callback?(error)
}
}
2 changes: 1 addition & 1 deletion lib/ios/Ldk.swift
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ enum EventTypes: String, CaseIterable {
case new_channel = "new_channel"
case network_graph_updated = "network_graph_updated"
case channel_manager_restarted = "channel_manager_restarted"
case backup_sync_persist_error = "backup_sync_persist_error"
case backup_state_update = "backup_state_update"
}
//*****************************************************************

12 changes: 12 additions & 0 deletions lib/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ export enum EEventTypes {
new_channel = 'new_channel',
network_graph_updated = 'network_graph_updated',
channel_manager_restarted = 'channel_manager_restarted',
backup_state_update = 'backup_state_update',
}

//LDK event responses
@@ -602,3 +603,14 @@ export type TNodeSignReq = {
message: string;
messagePrefix?: string;
};

type TBackupFileState = {
lastQueued: number;
lastPersisted?: number;
lastFailed?: number;
lastErrorMessage?: string;
};

export type TBackupStateUpdate = {
[key: string]: TBackupFileState;
};

0 comments on commit 668ddaf

Please sign in to comment.