From 356caf72bbdb57b3d5bb73147d2527c4ce58ca30 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 19 Jul 2024 14:58:13 +0200 Subject: [PATCH 1/4] feat: persist trusted peers to disk --- example/ldk/index.ts | 10 +++++++- lib/src/lightning-manager.ts | 48 ++++++++++++++++++++++++++++++++---- lib/src/utils/types.ts | 2 +- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/example/ldk/index.ts b/example/ldk/index.ts index c4369695..544bb221 100644 --- a/example/ldk/index.ts +++ b/example/ldk/index.ts @@ -150,7 +150,6 @@ export const setupLdk = async ( }, manually_accept_inbound_channels: true, }, - trustedZeroConfPeers: [peers.lnd.pubKey], skipRemoteBackups: !backupServerDetails, lspLogEvent: async (payload) => { console.log('Log event for LSP:', JSON.stringify(payload)); @@ -184,6 +183,15 @@ export const setupLdk = async ( return err(e.toString()); } + //Set trusted peers we should accept zero conf channels from + const trustedPeers = Object.values(peers).map((peer) => peer.pubKey); + const trustedPeersRes = await lm.setTrustedZeroConfPeerNodeIds( + trustedPeers, + ); + if (trustedPeersRes.isErr()) { + return err(trustedPeersRes.error.message); + } + const nodeIdRes = await ldk.nodeId(); if (nodeIdRes.isErr()) { return err(nodeIdRes.error.message); diff --git a/lib/src/lightning-manager.ts b/lib/src/lightning-manager.ts index 2b0e9426..aa86db5b 100644 --- a/lib/src/lightning-manager.ts +++ b/lib/src/lightning-manager.ts @@ -140,7 +140,6 @@ class LightningManager { minAllowedNonAnchorChannelRemoteFee: 5, onChainSweep: 5, }); - trustedZeroConfPeers: string[] = []; broadcastTransaction: TBroadcastTransaction = async (): Promise => {}; lspLogEvent: TLspLogEvent | undefined; pathFailedSubscription: EmitterSubscription | undefined; @@ -300,7 +299,6 @@ class LightningManager { scorerDownloadUrl = 'https://rapidsync.lightningdevkit.org/scoring/scorer.bin', forceCloseOnStartup, userConfig = defaultUserConfig, - trustedZeroConfPeers = [], skipParamCheck = false, skipRemoteBackups = false, lspLogEvent, @@ -378,7 +376,6 @@ class LightningManager { this.watchTxs = []; this.watchOutputs = []; this.confirmedWatchOutputs = await this.getConfirmedWatchOutputs(); - this.trustedZeroConfPeers = trustedZeroConfPeers; this.lspLogEvent = lspLogEvent; if (!this.baseStoragePath) { @@ -550,6 +547,37 @@ class LightningManager { return await ldk.updateFees(fees); } + /** + * Persists nodeIDs to disk that should be checked when accepting zero-conf channels. + * @param {string[]} nodeIds + * @param {boolean} [merge] If true, will merge provided nodeIds with existing nodeIds. Set false to overwrite entirely. + * @returns {Promise>} + */ + async setTrustedZeroConfPeerNodeIds( + nodeIds: string[], + merge: boolean = true, + ): Promise> { + let trustedNodeIds = nodeIds; + const accountPath = appendPath(this.baseStoragePath, this.account.name); + if (merge) { + const readRes = await ldk.readFromFile({ + fileName: ELdkFiles.trusted_peer_node_ids, + path: accountPath, + }); + if (readRes.isOk()) { + let currentIds = parseData(readRes.value.content, []); + trustedNodeIds = Array.from(new Set([...currentIds, ...nodeIds])); + } + } + + return await ldk.writeToFile({ + fileName: ELdkFiles.trusted_peer_node_ids, + path: accountPath, + content: JSON.stringify(trustedNodeIds), + remotePersist: false, + }); + } + /** * Fetches current best block and sends to LDK to update both channelManager and chainMonitor. * Also watches transactions and outputs for confirmed and unconfirmed transactions and updates LDK. @@ -2064,9 +2092,19 @@ class LightningManager { } = req; let trustedPeer0Conf = false; - const isTrustedPeer = - this.trustedZeroConfPeers.indexOf(counterparty_node_id) > -1; if (supports_zero_conf) { + let trustedZeroConfPeers: string[] = []; + const readRes = await ldk.readFromFile({ + fileName: ELdkFiles.trusted_peer_node_ids, + path: appendPath(this.baseStoragePath, this.account.name), + }); + if (readRes.isOk()) { + trustedZeroConfPeers = parseData(readRes.value.content, []); + } + + const isTrustedPeer = + trustedZeroConfPeers.indexOf(counterparty_node_id) > -1; + if (isTrustedPeer) { await ldk.writeToLogFile( 'info', diff --git a/lib/src/utils/types.ts b/lib/src/utils/types.ts index 9fa94a73..7c86f9e1 100644 --- a/lib/src/utils/types.ts +++ b/lib/src/utils/types.ts @@ -481,6 +481,7 @@ export enum ELdkFiles { channel_manager = 'channel_manager.bin', //Serialised rust object channels = 'channels', //Path containing multiple files of serialised channels peers = 'peers.json', //File saved from JS + trusted_peer_node_ids = 'trusted_peer_node_ids.json', //File saved from JS unconfirmed_transactions = 'unconfirmed_transactions.json', broadcasted_transactions = 'broadcasted_transactions.json', confirmed_broadcasted_transactions = 'confirmed_broadcasted_transactions.json', @@ -571,7 +572,6 @@ export type TLdkStart = { scorerDownloadUrl?: string; forceCloseOnStartup?: TForceCloseOnStartup; userConfig?: TUserConfig; - trustedZeroConfPeers?: string[]; skipParamCheck?: boolean; skipRemoteBackups?: boolean; lspLogEvent?: TLspLogEvent; From 25d16b4a5af3a017e07d38e1f4fa320a42d4a07b Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Wed, 31 Jul 2024 17:17:02 +0200 Subject: [PATCH 2/4] feat: add key for wallet data backup --- backup-server/src/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/backup-server/src/server.js b/backup-server/src/server.js index 12354309..b969e643 100644 --- a/backup-server/src/server.js +++ b/backup-server/src/server.js @@ -34,6 +34,7 @@ const ldkLabels = [ ]; const bitkitLabels = [ 'bitkit_settings', + 'bitkit_wallet', 'bitkit_widgets', 'bitkit_metadata', 'bitkit_blocktank_orders', From f8be09aec376fd75325649eb98a58c4cf076bedf Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 13 Aug 2024 11:30:19 +0200 Subject: [PATCH 3/4] feat: expose funding_output_index --- example/ios/Podfile.lock | 4 ++-- lib/android/src/main/java/com/reactnativeldk/Helpers.kt | 1 + lib/ios/Helpers.swift | 1 + lib/package.json | 2 +- lib/src/utils/types.ts | 1 + 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index f88f30a9..eb5623e8 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -316,7 +316,7 @@ PODS: - React-jsinspector (0.72.4) - React-logger (0.72.4): - glog - - react-native-ldk (0.0.145): + - react-native-ldk (0.0.148): - React - react-native-randombytes (3.6.1): - React-Core @@ -621,7 +621,7 @@ SPEC CHECKSUMS: React-jsiexecutor: c7f826e40fa9cab5d37cab6130b1af237332b594 React-jsinspector: aaed4cf551c4a1c98092436518c2d267b13a673f React-logger: da1ebe05ae06eb6db4b162202faeafac4b435e77 - react-native-ldk: 496216796eafbd77c43cd5228342460a242cf7ed + react-native-ldk: fda4d4381d40401bdc5c3a9965937d19b232ed08 react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846 react-native-tcp-socket: c1b7297619616b4c9caae6889bcb0aba78086989 React-NativeModulesApple: edb5ace14f73f4969df6e7b1f3e41bef0012740f diff --git a/lib/android/src/main/java/com/reactnativeldk/Helpers.kt b/lib/android/src/main/java/com/reactnativeldk/Helpers.kt index 992683bb..d4452427 100644 --- a/lib/android/src/main/java/com/reactnativeldk/Helpers.kt +++ b/lib/android/src/main/java/com/reactnativeldk/Helpers.kt @@ -123,6 +123,7 @@ val ChannelDetails.asJson: WritableMap result.putInt("balance_sat", (_balance_msat / 1000).toInt()) result.putHexString("counterparty_node_id", _counterparty._node_id) result.putHexString("funding_txid", _funding_txo?._txid?.reversed()?.toByteArray()) + _funding_txo?._index?.toInt()?.let { result.putInt("funding_output_index", it) } result.putHexString("channel_type", _channel_type?.write()) result.putString("user_channel_id", _user_channel_id.leBytes.hexEncodedString()) result.putInt("confirmations_required", (_confirmations_required as Option_u32Z.Some).some) diff --git a/lib/ios/Helpers.swift b/lib/ios/Helpers.swift index 328210fd..e9170bd7 100644 --- a/lib/ios/Helpers.swift +++ b/lib/ios/Helpers.swift @@ -122,6 +122,7 @@ extension ChannelDetails { "balance_sat": getBalanceMsat() / 1000, "counterparty_node_id": Data(getCounterparty().getNodeId()).hexEncodedString(), "funding_txid": Data(getFundingTxo()?.getTxid()?.reversed() ?? []).hexEncodedString(), + "funding_output_index": getFundingTxo()?.getIndex() as Any, // Optional number "channel_type": Data(getChannelType()?.write() ?? []).hexEncodedString(), "user_channel_id": Data(getUserChannelId()).hexEncodedString(), //String "confirmations_required": getConfirmationsRequired() as Any, // Optional number diff --git a/lib/package.json b/lib/package.json index f5babd76..0aa9006d 100644 --- a/lib/package.json +++ b/lib/package.json @@ -1,7 +1,7 @@ { "name": "@synonymdev/react-native-ldk", "title": "React Native LDK", - "version": "0.0.146", + "version": "0.0.148", "description": "React Native wrapper for LDK", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/lib/src/utils/types.ts b/lib/src/utils/types.ts index 7c86f9e1..d9be84aa 100644 --- a/lib/src/utils/types.ts +++ b/lib/src/utils/types.ts @@ -150,6 +150,7 @@ export type TChannel = { balance_sat: number; counterparty_node_id: string; funding_txid?: string; + funding_output_index? : number; channel_type?: string; user_channel_id: string; confirmations_required?: number; From 79c075f462d8744ebcb458182af24cd45bb29ace Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 16 Aug 2024 14:12:58 +0200 Subject: [PATCH 4/4] fix: spend recovered outputs script checks unconfirmed and confirmed broadcasted txs --- .../main/java/com/reactnativeldk/LdkModule.kt | 26 +++++--- lib/ios/Ldk.m | 1 + lib/ios/Ldk.swift | 15 +++-- lib/src/ldk.ts | 2 + lib/src/lightning-manager.ts | 62 ++++++++++++------- lib/src/utils/types.ts | 3 +- 6 files changed, 75 insertions(+), 34 deletions(-) diff --git a/lib/android/src/main/java/com/reactnativeldk/LdkModule.kt b/lib/android/src/main/java/com/reactnativeldk/LdkModule.kt index 588a4e4e..eca731f3 100644 --- a/lib/android/src/main/java/com/reactnativeldk/LdkModule.kt +++ b/lib/android/src/main/java/com/reactnativeldk/LdkModule.kt @@ -1228,7 +1228,7 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod } @ReactMethod - fun spendRecoveredForceCloseOutputs(transaction: String, confirmationHeight: Double, changeDestinationScript: String, promise: Promise) { + fun spendRecoveredForceCloseOutputs(transaction: String, confirmationHeight: Double, changeDestinationScript: String, useInner: Boolean, promise: Promise) { if (channelStoragePath == "") { return handleReject(promise, LdkErrors.init_storage_path) } @@ -1270,13 +1270,23 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod continue } - val res = keysManager!!.spend_spendable_outputs( - descriptors, - emptyArray(), - changeDestinationScript.hexa(), - feeEstimator.onChainSweep, - Option_u32Z.none() - ) + val res = if (useInner) { + keysManager!!.inner.spend_spendable_outputs( + descriptors, + emptyArray(), + changeDestinationScript.hexa(), + feeEstimator.onChainSweep, + Option_u32Z.none() + ) + } else { + keysManager!!.spend_spendable_outputs( + descriptors, + emptyArray(), + changeDestinationScript.hexa(), + feeEstimator.onChainSweep, + Option_u32Z.none() + ) + } if (res.is_ok) { txs.pushHexString((res as Result_TransactionNoneZ.Result_TransactionNoneZ_OK).res) diff --git a/lib/ios/Ldk.m b/lib/ios/Ldk.m index 34314708..fcc64328 100644 --- a/lib/ios/Ldk.m +++ b/lib/ios/Ldk.m @@ -176,6 +176,7 @@ @interface RCT_EXTERN_MODULE(Ldk, NSObject) RCT_EXTERN_METHOD(spendRecoveredForceCloseOutputs:(NSString *)transaction confirmationHeight:(NSInteger *)confirmationHeight changeDestinationScript:(NSString *)changeDestinationScript + useInner:(BOOL *)useInner resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(nodeSign:(NSString *)message diff --git a/lib/ios/Ldk.swift b/lib/ios/Ldk.swift index 6c75a60e..f9453dff 100644 --- a/lib/ios/Ldk.swift +++ b/lib/ios/Ldk.swift @@ -507,7 +507,7 @@ class Ldk: NSObject { currentBlockchainHeight = blockHeight addForegroundObserver() startDroppedPeerTimer() - + return handleResolve(resolve, .channel_manager_init_success) } @@ -659,7 +659,7 @@ class Ldk: NSObject { 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, @@ -1385,7 +1385,7 @@ class Ldk: NSObject { } @objc - func spendRecoveredForceCloseOutputs(_ transaction: NSString, confirmationHeight: NSInteger, changeDestinationScript: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + func spendRecoveredForceCloseOutputs(_ transaction: NSString, confirmationHeight: NSInteger, changeDestinationScript: NSString, useInner: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { guard let channelStoragePath = Ldk.channelStoragePath, let keysManager, let channelManager else { return handleReject(reject, .init_storage_path) @@ -1428,7 +1428,14 @@ class Ldk: NSObject { continue } - let res = keysManager.spendSpendableOutputs( + let res = useInner ? keysManager.inner.spendSpendableOutputs( + descriptors: descriptors, + outputs: [], + changeDestinationScript: String(changeDestinationScript).hexaBytes, + feerateSatPer1000Weight: feeEstimator.getEstSatPer1000Weight(confirmationTarget: .OnChainSweep), + locktime: nil) + : + keysManager.spendSpendableOutputs( descriptors: descriptors, outputs: [], changeDestinationScript: String(changeDestinationScript).hexaBytes, diff --git a/lib/src/ldk.ts b/lib/src/ldk.ts index ab6dae17..4d294357 100644 --- a/lib/src/ldk.ts +++ b/lib/src/ldk.ts @@ -1249,12 +1249,14 @@ class LDK { transaction, confirmationHeight, changeDestinationScript, + useInner, }: TSpendRecoveredForceCloseOutputsReq): Promise> { try { const res = await NativeLDK.spendRecoveredForceCloseOutputs( transaction, confirmationHeight, changeDestinationScript, + useInner, ); this.writeDebugToLog('spendRecoveredForceCloseOutputs', res); return ok(res); diff --git a/lib/src/lightning-manager.ts b/lib/src/lightning-manager.ts index aa86db5b..a977a221 100644 --- a/lib/src/lightning-manager.ts +++ b/lib/src/lightning-manager.ts @@ -1637,14 +1637,26 @@ class LightningManager { * Returns previously broadcasted transactions saved in storgare. * @returns {Promise} */ - async getLdkBroadcastedTxs(): Promise { + async getLdkBroadcastedTxs( + includeConfirmed: boolean = false, + ): Promise { + let txs: TLdkBroadcastedTransactions = []; const res = await ldk.readFromFile({ fileName: ELdkFiles.broadcasted_transactions, }); if (res.isOk()) { - return parseData(res.value.content, []); + txs = parseData(res.value.content, []); } - return []; + + if (includeConfirmed) { + const confirmedRes = await ldk.readFromFile({ + fileName: ELdkFiles.confirmed_broadcasted_transactions, + }); + if (confirmedRes.isOk()) { + txs = txs.concat(parseData(confirmedRes.value.content, [])); + } + } + return txs; } async cleanupBroadcastedTxs(): Promise { @@ -1766,33 +1778,41 @@ class LightningManager { return err('Unable to retrieve change_destination_script.'); } - const txs = await this.getLdkBroadcastedTxs(); + const txs = await this.getLdkBroadcastedTxs(true); if (!txs.length) { return ok('No outputs to reconstruct as no cached transactions found.'); } let txsToBroadcast = 0; - for (const hexTx of txs) { - const tx = bitcoin.Transaction.fromHex(hexTx); - const txData = await this.getTransactionData(tx.getId()); - const txsRes = await ldk.spendRecoveredForceCloseOutputs({ - transaction: hexTx, - confirmationHeight: txData?.height ?? 0, - changeDestinationScript, - }); + const processTxs = async (useInner: boolean): Promise => { + for (const hexTx of txs) { + const tx = bitcoin.Transaction.fromHex(hexTx); + const txData = await this.getTransactionData(tx.getId()); - if (txsRes.isErr()) { - await ldk.writeToLogFile('error', txsRes.error.message); - console.error(txsRes.error.message); - continue; - } + const txsRes = await ldk.spendRecoveredForceCloseOutputs({ + transaction: hexTx, + confirmationHeight: txData?.height ?? 0, + changeDestinationScript, + useInner, + }); + + if (txsRes.isErr()) { + await ldk.writeToLogFile('error', txsRes.error.message); + console.error(txsRes.error.message); + continue; + } - for (const createdTx of txsRes.value) { - txsToBroadcast++; - await this.broadcastTransaction(createdTx); + for (const createdTx of txsRes.value) { + txsToBroadcast++; + await this.broadcastTransaction(createdTx); + } } - } + }; + + //Process first using the inner (ldk keychain) and then using the custom keychain + await processTxs(true); + await processTxs(false); return ok(`Attempting to reconstruct ${txsToBroadcast} transactions.`); } diff --git a/lib/src/utils/types.ts b/lib/src/utils/types.ts index d9be84aa..8cb617b1 100644 --- a/lib/src/utils/types.ts +++ b/lib/src/utils/types.ts @@ -150,7 +150,7 @@ export type TChannel = { balance_sat: number; counterparty_node_id: string; funding_txid?: string; - funding_output_index? : number; + funding_output_index?: number; channel_type?: string; user_channel_id: string; confirmations_required?: number; @@ -613,6 +613,7 @@ export type TSpendRecoveredForceCloseOutputsReq = { transaction: string; confirmationHeight: number; changeDestinationScript: string; + useInner: boolean; }; export type TBackupServerDetails = {