From 616d14d7bc38a581235889f4f18ed09526529332 Mon Sep 17 00:00:00 2001 From: Evan Kaloudis Date: Fri, 19 Jan 2024 19:31:53 -0500 Subject: [PATCH 1/3] Lightning Node Connect: LSP support --- backends/LightningNodeConnect.ts | 2 +- ios/Podfile.lock | 4 +- ios/zeus.xcodeproj/project.pbxproj | 2 + lndmobile/LndMobileInjection.ts | 7 ++++ lndmobile/index.ts | 22 ++++++++++ stores/LSPStore.ts | 65 +++++++++++++++++++++++------- 6 files changed, 85 insertions(+), 17 deletions(-) diff --git a/backends/LightningNodeConnect.ts b/backends/LightningNodeConnect.ts index 6e89cf5a5..2cb3cc962 100644 --- a/backends/LightningNodeConnect.ts +++ b/backends/LightningNodeConnect.ts @@ -388,7 +388,7 @@ export default class LightningNodeConnect { supportsAddressTypeSelection = () => true; supportsTaproot = () => this.supports('v0.15.0'); supportsBumpFee = () => true; - supportsLSPs = () => false; + supportsLSPs = () => true; supportsNetworkInfo = () => false; supportsSimpleTaprootChannels = () => this.supports('v0.17.0'); supportsCustomPreimages = () => true; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d19f1cf5e..989b21494 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -520,7 +520,7 @@ PODS: - RNVectorIcons (7.1.0): - React - SocketRocket (0.6.1) - - SwiftProtobuf (1.23.0) + - SwiftProtobuf (1.25.2) - TcpSockets (4.0.0): - React - Yoga (1.14.0) @@ -830,7 +830,7 @@ SPEC CHECKSUMS: RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396 RNVectorIcons: bc69e6a278b14842063605de32bec61f0b251a59 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 - SwiftProtobuf: b70d65f419fbfe61a2d58003456ca5da58e337d6 + SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1 TcpSockets: 4ef55305239923b343ed0a378b1fac188b1373b0 Yoga: 86fed2e4d425ee4c6eab3813ba1791101ee153c6 diff --git a/ios/zeus.xcodeproj/project.pbxproj b/ios/zeus.xcodeproj/project.pbxproj index cf025e61d..17ebd3d04 100644 --- a/ios/zeus.xcodeproj/project.pbxproj +++ b/ios/zeus.xcodeproj/project.pbxproj @@ -2134,6 +2134,7 @@ "-ld_classic", "-Wl", "-ld_classic", + "-Wl -ld_classic ", ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; @@ -2211,6 +2212,7 @@ "-ld_classic", "-Wl", "-ld_classic", + "-Wl -ld_classic ", ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; diff --git a/lndmobile/LndMobileInjection.ts b/lndmobile/LndMobileInjection.ts index c63a745e0..9798ef63b 100644 --- a/lndmobile/LndMobileInjection.ts +++ b/lndmobile/LndMobileInjection.ts @@ -36,6 +36,7 @@ import { listPayments, listInvoices, subscribeChannelGraph, + channelAcceptorAnswer, sendKeysendPaymentV2 } from './index'; import { @@ -196,6 +197,11 @@ export interface ILndMobileInjections { ) => Promise; listPayments: () => Promise; subscribeChannelGraph: () => Promise; + channelAcceptorAnswer: ( + pending_chan_id: Uint8Array, + zero_conf: boolean, + accept: boolean + ) => void; sendKeysendPaymentV2: ({ amt, max_shard_size_msat, @@ -382,6 +388,7 @@ export default { listPayments, listInvoices, subscribeChannelGraph, + channelAcceptorAnswer, sendKeysendPaymentV2 }, channel: { diff --git a/lndmobile/index.ts b/lndmobile/index.ts index ab8dc1585..84fddd67e 100644 --- a/lndmobile/index.ts +++ b/lndmobile/index.ts @@ -841,6 +841,28 @@ export const subscribeChannelGraph = async (): Promise => { return response; }; +/** + * @throws + */ +export const channelAcceptorAnswer = async ( + pending_chan_id: Uint8Array, + zero_conf: boolean, + accept: boolean +) => { + await sendStreamCommand< + lnrpc.IChannelAcceptResponse, + lnrpc.ChannelAcceptResponse + >({ + request: lnrpc.ChannelAcceptResponse, + method: 'ChannelAcceptor', + options: { + pending_chan_id, + zero_conf, + accept + } + }); +}; + export type IReadLndLogResponse = string[]; /** * @throws diff --git a/stores/LSPStore.ts b/stores/LSPStore.ts index 6b15c14a2..4e7d8427a 100644 --- a/stores/LSPStore.ts +++ b/stores/LSPStore.ts @@ -1,12 +1,13 @@ import { action, observable } from 'mobx'; import ReactNativeBlobUtil from 'react-native-blob-util'; +import { NativeEventEmitter, NativeModules } from 'react-native'; import SettingsStore from './SettingsStore'; import ChannelsStore from './ChannelsStore'; import stores from './Stores'; import lndMobile from '../lndmobile/LndMobileInjection'; -const { channel } = lndMobile; +const { channel, index } = lndMobile; import Base64Utils from '../utils/Base64Utils'; import { LndMobileEventEmitter } from '../utils/LndMobileUtils'; @@ -170,22 +171,58 @@ export default class LSPStore { @action public initChannelAcceptor = async () => { + const { implementation } = this.settingsStore; if (this.channelAcceptor) return; - this.channelAcceptor = LndMobileEventEmitter.addListener( - 'ChannelAcceptor', - async (event: any) => { - try { - const channelAcceptRequest = - channel.decodeChannelAcceptRequest(event.data); - - await this.handleChannelAcceptorEvent(channelAcceptRequest); - } catch (error: any) { - console.error('channel acceptance error: ' + error.message); + + if (implementation === 'embedded-lnd') { + this.channelAcceptor = LndMobileEventEmitter.addListener( + 'ChannelAcceptor', + async (event: any) => { + try { + const result = channel.decodeChannelAcceptRequest( + event.data + ); + await this.handleChannelAcceptorEvent(result); + } catch (error: any) { + console.error( + 'channelAcceptorEvent embedded-lnd error:', + error.message + ); + } } - } - ); + ); + + await channel.channelAcceptor(); + } + + if (implementation === 'lightning-node-connect') { + const { LncModule } = NativeModules; + const eventEmitter = new NativeEventEmitter(LncModule); + this.channelAcceptor = eventEmitter.addListener( + 'lnrpc.Lightning.ChannelAcceptor', + async (event: any) => { + if (event.result) { + try { + const result = JSON.parse(event.result); + // only allow zero conf chans from the LSP + const isZeroConfAllowed = + result.node_pubkey === this.info.pub_key; - await channel.channelAcceptor(); + index.channelAcceptorAnswer( + result.pending_chan_id, + !result.wants_zero_conf || isZeroConfAllowed, + isZeroConfAllowed + ); + } catch (error: any) { + console.error( + 'channelAcceptorEvent lightning-node-connect error:', + error.message + ); + } + } + } + ); + } }; @action From 7e4c907b2c08ff29c2295d4fb360286d83b89a6d Mon Sep 17 00:00:00 2001 From: Evan Kaloudis Date: Sat, 20 Jan 2024 15:38:49 -0500 Subject: [PATCH 2/3] WIP --- backends/LightningNodeConnect.ts | 9 ++++++ stores/LSPStore.ts | 24 ++++++++++---- utils/BackendUtils.ts | 3 ++ views/Wallet/Wallet.tsx | 55 ++++++++++++++++++++++++++++---- 4 files changed, 78 insertions(+), 13 deletions(-) diff --git a/backends/LightningNodeConnect.ts b/backends/LightningNodeConnect.ts index 2cb3cc962..06ba3367f 100644 --- a/backends/LightningNodeConnect.ts +++ b/backends/LightningNodeConnect.ts @@ -354,6 +354,15 @@ export default class LightningNodeConnect { await this.lnc.lnd.lightning .lookupInvoice({ r_hash: Base64Utils.hexToBase64(data.r_hash) }) .then((data: lnrpc.Invoice) => snakeize(data)); + channelAcceptor = (data: lnrpc.channelAcceptRequest) => this.lnc.lnd.lightning.channelAcceptor(data); + channelAcceptorAnswer = (data: lnrpc.channelAcceptorResponse) => { + console.log('backend', data); + return this.lnc.lnd.lightning.channelAcceptor({ + pending_chan_id: data.pending_chan_id, + zero_conf: data.zero_conf, + accept: data.accept + }); + }; subscribeInvoice = (r_hash: string) => this.lnc.lnd.invoices.subscribeSingleInvoice({ r_hash }); subscribeInvoices = () => this.lnc.lnd.lightning.subscribeInvoices(); diff --git a/stores/LSPStore.ts b/stores/LSPStore.ts index 4e7d8427a..bc04bc42b 100644 --- a/stores/LSPStore.ts +++ b/stores/LSPStore.ts @@ -7,8 +7,9 @@ import ChannelsStore from './ChannelsStore'; import stores from './Stores'; import lndMobile from '../lndmobile/LndMobileInjection'; -const { channel, index } = lndMobile; +const { channel } = lndMobile; +import BackendUtils from '../utils/BackendUtils'; import Base64Utils from '../utils/Base64Utils'; import { LndMobileEventEmitter } from '../utils/LndMobileUtils'; import { localeString } from '../utils/LocaleUtils'; @@ -198,21 +199,28 @@ export default class LSPStore { if (implementation === 'lightning-node-connect') { const { LncModule } = NativeModules; const eventEmitter = new NativeEventEmitter(LncModule); + console.log('hERE', eventEmitter); + const call = BackendUtils.channelAcceptor(); + console.log('call', call) this.channelAcceptor = eventEmitter.addListener( 'lnrpc.Lightning.ChannelAcceptor', - async (event: any) => { + (event: any) => { + // console.log('-->', event); if (event.result) { try { const result = JSON.parse(event.result); + console.log('~~RESULT', result); // only allow zero conf chans from the LSP const isZeroConfAllowed = result.node_pubkey === this.info.pub_key; - index.channelAcceptorAnswer( - result.pending_chan_id, - !result.wants_zero_conf || isZeroConfAllowed, - isZeroConfAllowed - ); + BackendUtils.channelAcceptorAnswer({ + pending_chan_id: result.pending_chan_id, + zero_conf: + !result.wants_zero_conf || + isZeroConfAllowed, + accept: isZeroConfAllowed + }); } catch (error: any) { console.error( 'channelAcceptorEvent lightning-node-connect error:', @@ -222,6 +230,8 @@ export default class LSPStore { } } ); + + console.log('~~~this.channelAcceptor', this.channelAcceptor); } }; diff --git a/utils/BackendUtils.ts b/utils/BackendUtils.ts index fc310aacb..df49c5600 100644 --- a/utils/BackendUtils.ts +++ b/utils/BackendUtils.ts @@ -106,6 +106,9 @@ class BackendUtils { this.call('publishTransaction', args); bumpFee = (...args: any[]) => this.call('bumpFee', args); lookupInvoice = (...args: any[]) => this.call('lookupInvoice', args); + channelAcceptor = (...args: any[]) => this.call('channelAcceptor', args); + channelAcceptorAnswer = (...args: any[]) => + this.call('channelAcceptorAnswer', args); subscribeInvoice = (...args: any[]) => this.call('subscribeInvoice', args); subscribeInvoices = (...args: any[]) => this.call('subscribeInvoices', args); diff --git a/views/Wallet/Wallet.tsx b/views/Wallet/Wallet.tsx index 440f8c36f..6a755f34f 100644 --- a/views/Wallet/Wallet.tsx +++ b/views/Wallet/Wallet.tsx @@ -4,7 +4,9 @@ import { AppState, BackHandler, Linking, + NativeEventEmitter, NativeEventSubscription, + NativeModules, PanResponder, PanResponderInstance, Platform, @@ -120,6 +122,8 @@ export default class Wallet extends React.Component { private handleAppStateChangeSubscription: NativeEventSubscription; private backPressSubscription: NativeEventSubscription; + channelAcceptor: any; + constructor(props) { super(props); this.state = { @@ -369,12 +373,6 @@ export default class Wallet extends React.Component { embeddedLndNetwork === 'Testnet' ); } - if (BackendUtils.supportsLSPs()) { - if (SettingsStore.settings.enableLSP) { - LSPStore.getLSPInfo(); - } - LSPStore.initChannelAcceptor(); - } NodeInfoStore.getNodeInfo(); if (BackendUtils.supportsAccounts()) UTXOsStore.listAccounts(); await BalanceStore.getCombinedBalance(false); @@ -455,6 +453,51 @@ export default class Wallet extends React.Component { } } + if (BackendUtils.supportsLSPs()) { + if (SettingsStore.settings.enableLSP) { + await LSPStore.getLSPInfo(); + } + + if (implementation === 'lightning-node-connect' && !this.channelAcceptor) { + const { LncModule } = NativeModules; + const eventEmitter = new NativeEventEmitter(LncModule); + console.log('hERE~~2', eventEmitter); + const call = BackendUtils.channelAcceptor(); + console.log('call', call) + this.channelAcceptor = eventEmitter.addListener( + 'lnrpc.Lightning.SubscribePeerEvents', + (event: any) => { + if (event.result) { + try { + const result = JSON.parse(event.result); + console.log('~~RESULT', result); + // // only allow zero conf chans from the LSP + // const isZeroConfAllowed = + // result.node_pubkey === this.info.pub_key; + + // BackendUtils.channelAcceptorAnswer({ + // pending_chan_id: result.pending_chan_id, + // zero_conf: + // !result.wants_zero_conf || + // isZeroConfAllowed, + // accept: isZeroConfAllowed + // }); + } catch (error: any) { + console.error( + 'channelAcceptorEvent lightning-node-connect error:', + error.message + ); + } + } + } + ); + + console.log('~~~this.channelAcceptor', this.channelAcceptor); + } else { + LSPStore.initChannelAcceptor(); + } + } + if (connecting) { setConnectingStatus(false); } From 9421f79751c721899a1730072c5415ac4e6225ba Mon Sep 17 00:00:00 2001 From: Evan Kaloudis Date: Tue, 13 Feb 2024 15:00:07 -0500 Subject: [PATCH 3/3] WIP --- backends/LightningNodeConnect.ts | 7 ++-- ios/Podfile.lock | 2 +- stores/LSPStore.ts | 70 ++++++++++++++++---------------- views/Wallet/Wallet.tsx | 26 ++++++------ 4 files changed, 54 insertions(+), 51 deletions(-) diff --git a/backends/LightningNodeConnect.ts b/backends/LightningNodeConnect.ts index 06ba3367f..d05d211a4 100644 --- a/backends/LightningNodeConnect.ts +++ b/backends/LightningNodeConnect.ts @@ -354,10 +354,11 @@ export default class LightningNodeConnect { await this.lnc.lnd.lightning .lookupInvoice({ r_hash: Base64Utils.hexToBase64(data.r_hash) }) .then((data: lnrpc.Invoice) => snakeize(data)); - channelAcceptor = (data: lnrpc.channelAcceptRequest) => this.lnc.lnd.lightning.channelAcceptor(data); + channelAcceptor = (data: lnrpc.channelAcceptRequest) => + this.lnc.lnd.lightning.channelAcceptor(data); channelAcceptorAnswer = (data: lnrpc.channelAcceptorResponse) => { - console.log('backend', data); - return this.lnc.lnd.lightning.channelAcceptor({ + console.log('^^^backend', data); + return this.lnc.call('lnrpc.Lightning.ChannelAcceptor', { pending_chan_id: data.pending_chan_id, zero_conf: data.zero_conf, accept: data.accept diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 989b21494..fdcbc9166 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -836,4 +836,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 643f83a7955aa123651bac4a54204e22598914df -COCOAPODS: 1.12.1 +COCOAPODS: 1.15.2 diff --git a/stores/LSPStore.ts b/stores/LSPStore.ts index bc04bc42b..bc36488ed 100644 --- a/stores/LSPStore.ts +++ b/stores/LSPStore.ts @@ -196,43 +196,43 @@ export default class LSPStore { await channel.channelAcceptor(); } - if (implementation === 'lightning-node-connect') { - const { LncModule } = NativeModules; - const eventEmitter = new NativeEventEmitter(LncModule); - console.log('hERE', eventEmitter); - const call = BackendUtils.channelAcceptor(); - console.log('call', call) - this.channelAcceptor = eventEmitter.addListener( - 'lnrpc.Lightning.ChannelAcceptor', - (event: any) => { - // console.log('-->', event); - if (event.result) { - try { - const result = JSON.parse(event.result); - console.log('~~RESULT', result); - // only allow zero conf chans from the LSP - const isZeroConfAllowed = - result.node_pubkey === this.info.pub_key; + // if (implementation === 'lightning-node-connect') { + // const { LncModule } = NativeModules; + // const eventEmitter = new NativeEventEmitter(LncModule); + // console.log('hERE', eventEmitter); + // const call = BackendUtils.channelAcceptor(); + // console.log('call', call) + // this.channelAcceptor = eventEmitter.addListener( + // 'lnrpc.Lightning.ChannelAcceptor', + // (event: any) => { + // // console.log('-->', event); + // if (event.result) { + // try { + // const result = JSON.parse(event.result); + // console.log('~~RESULT', result); + // // only allow zero conf chans from the LSP + // const isZeroConfAllowed = + // result.node_pubkey === this.info.pub_key; - BackendUtils.channelAcceptorAnswer({ - pending_chan_id: result.pending_chan_id, - zero_conf: - !result.wants_zero_conf || - isZeroConfAllowed, - accept: isZeroConfAllowed - }); - } catch (error: any) { - console.error( - 'channelAcceptorEvent lightning-node-connect error:', - error.message - ); - } - } - } - ); + // BackendUtils.channelAcceptorAnswer({ + // pending_chan_id: result.pending_chan_id, + // zero_conf: + // !result.wants_zero_conf || + // isZeroConfAllowed, + // accept: isZeroConfAllowed + // }); + // } catch (error: any) { + // console.error( + // 'channelAcceptorEvent lightning-node-connect error:', + // error.message + // ); + // } + // } + // } + // ); - console.log('~~~this.channelAcceptor', this.channelAcceptor); - } + // console.log('~~~this.channelAcceptor', this.channelAcceptor); + // } }; @action diff --git a/views/Wallet/Wallet.tsx b/views/Wallet/Wallet.tsx index 6a755f34f..62346c9c0 100644 --- a/views/Wallet/Wallet.tsx +++ b/views/Wallet/Wallet.tsx @@ -458,30 +458,32 @@ export default class Wallet extends React.Component { await LSPStore.getLSPInfo(); } - if (implementation === 'lightning-node-connect' && !this.channelAcceptor) { + if (implementation === 'lightning-node-connect') { + // if (this.channelAcceptor) return; const { LncModule } = NativeModules; const eventEmitter = new NativeEventEmitter(LncModule); - console.log('hERE~~2', eventEmitter); + console.log('hERE~~3', eventEmitter); const call = BackendUtils.channelAcceptor(); console.log('call', call) this.channelAcceptor = eventEmitter.addListener( - 'lnrpc.Lightning.SubscribePeerEvents', + call, (event: any) => { + console.log('>>event', event); if (event.result) { try { const result = JSON.parse(event.result); console.log('~~RESULT', result); // // only allow zero conf chans from the LSP - // const isZeroConfAllowed = - // result.node_pubkey === this.info.pub_key; + const isZeroConfAllowed = + result.node_pubkey === LSPStore.info.pub_key; - // BackendUtils.channelAcceptorAnswer({ - // pending_chan_id: result.pending_chan_id, - // zero_conf: - // !result.wants_zero_conf || - // isZeroConfAllowed, - // accept: isZeroConfAllowed - // }); + BackendUtils.channelAcceptorAnswer({ + pending_chan_id: result.pending_chan_id, + zero_conf: + !result.wants_zero_conf || + isZeroConfAllowed, + accept: isZeroConfAllowed + }); } catch (error: any) { console.error( 'channelAcceptorEvent lightning-node-connect error:',