Skip to content

Commit 0050220

Browse files
committed
LocalLnd implementatoin and refactoring
1 parent 9d06fdc commit 0050220

File tree

11 files changed

+447
-288
lines changed

11 files changed

+447
-288
lines changed

LightningKit.xcodeproj/project.pbxproj

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
2FA5DC8A66918200D42EA1EE /* MessageResponseStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5D6FACBD18084B3922C1B /* MessageResponseStream.swift */; };
11+
D059940C2403C9DE0096AC17 /* Lndmobile.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D059940B2403C9DE0096AC17 /* Lndmobile.framework */; };
1012
D3A7339423FA7D94005DAC30 /* LightningKit.h in Headers */ = {isa = PBXBuildFile; fileRef = D3A7339223FA7D94005DAC30 /* LightningKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
1113
D3A733B123FA7E98005DAC30 /* RemoteLnd.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3A7339B23FA7E98005DAC30 /* RemoteLnd.swift */; };
1214
D3A733B223FA7E98005DAC30 /* WalletUnlocker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3A7339C23FA7E98005DAC30 /* WalletUnlocker.swift */; };
@@ -26,13 +28,13 @@
2628
D3A733C023FA7E98005DAC30 /* LndMobileCallbackError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3A733AE23FA7E98005DAC30 /* LndMobileCallbackError.swift */; };
2729
D3A733C123FA7E98005DAC30 /* VoidResponseCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3A733AF23FA7E98005DAC30 /* VoidResponseCallback.swift */; };
2830
D3A733C223FA7E98005DAC30 /* LocalLnd.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3A733B023FA7E98005DAC30 /* LocalLnd.swift */; };
29-
D3A733CA23FA80A2005DAC30 /* GRPC in Frameworks */ = {isa = PBXBuildFile; productRef = D3A733C923FA80A2005DAC30 /* GRPC */; };
30-
D3A733CF23FA863C005DAC30 /* Lndmobile.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3A733C323FA7F2F005DAC30 /* Lndmobile.framework */; };
3131
E989B9E19F21A2518A31A210 /* Pods_LightningKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05C723F70B6969736B4E735 /* Pods_LightningKit.framework */; };
3232
/* End PBXBuildFile section */
3333

3434
/* Begin PBXFileReference section */
3535
1BAB348B97601BA830BE5AB9 /* Pods-LightningKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LightningKit.debug.xcconfig"; path = "Target Support Files/Pods-LightningKit/Pods-LightningKit.debug.xcconfig"; sourceTree = "<group>"; };
36+
2FA5D6FACBD18084B3922C1B /* MessageResponseStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageResponseStream.swift; sourceTree = "<group>"; };
37+
D059940B2403C9DE0096AC17 /* Lndmobile.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Lndmobile.framework; path = Carthage/Build/iOS/Lndmobile.framework; sourceTree = SOURCE_ROOT; };
3638
D05C723F70B6969736B4E735 /* Pods_LightningKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LightningKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3739
D3A7338F23FA7D94005DAC30 /* LightningKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LightningKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3840
D3A7339223FA7D94005DAC30 /* LightningKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LightningKit.h; sourceTree = "<group>"; };
@@ -55,7 +57,6 @@
5557
D3A733AE23FA7E98005DAC30 /* LndMobileCallbackError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LndMobileCallbackError.swift; sourceTree = "<group>"; };
5658
D3A733AF23FA7E98005DAC30 /* VoidResponseCallback.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoidResponseCallback.swift; sourceTree = "<group>"; };
5759
D3A733B023FA7E98005DAC30 /* LocalLnd.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalLnd.swift; sourceTree = "<group>"; };
58-
D3A733C323FA7F2F005DAC30 /* Lndmobile.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Lndmobile.framework; path = Carthage/Build/iOS/Lndmobile.framework; sourceTree = SOURCE_ROOT; };
5960
ED5614F3904FD5A9187834D8 /* Pods-LightningKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LightningKit.release.xcconfig"; path = "Target Support Files/Pods-LightningKit/Pods-LightningKit.release.xcconfig"; sourceTree = "<group>"; };
6061
/* End PBXFileReference section */
6162

@@ -64,8 +65,7 @@
6465
isa = PBXFrameworksBuildPhase;
6566
buildActionMask = 2147483647;
6667
files = (
67-
D3A733CA23FA80A2005DAC30 /* GRPC in Frameworks */,
68-
D3A733CF23FA863C005DAC30 /* Lndmobile.framework in Frameworks */,
68+
D059940C2403C9DE0096AC17 /* Lndmobile.framework in Frameworks */,
6969
E989B9E19F21A2518A31A210 /* Pods_LightningKit.framework in Frameworks */,
7070
);
7171
runOnlyForDeploymentPostprocessing = 0;
@@ -103,7 +103,7 @@
103103
D3A7339123FA7D94005DAC30 /* LightningKit */ = {
104104
isa = PBXGroup;
105105
children = (
106-
D3A733C323FA7F2F005DAC30 /* Lndmobile.framework */,
106+
D059940B2403C9DE0096AC17 /* Lndmobile.framework */,
107107
D3A733A523FA7E98005DAC30 /* Extensions */,
108108
D3A733AB23FA7E98005DAC30 /* Local */,
109109
D3A733A123FA7E98005DAC30 /* Proto */,
@@ -161,9 +161,10 @@
161161
D3A733AC23FA7E98005DAC30 /* LndMobileCallbacks */ = {
162162
isa = PBXGroup;
163163
children = (
164+
D3A733AF23FA7E98005DAC30 /* VoidResponseCallback.swift */,
164165
D3A733AD23FA7E98005DAC30 /* MessageResponseCallback.swift */,
166+
2FA5D6FACBD18084B3922C1B /* MessageResponseStream.swift */,
165167
D3A733AE23FA7E98005DAC30 /* LndMobileCallbackError.swift */,
166-
D3A733AF23FA7E98005DAC30 /* VoidResponseCallback.swift */,
167168
);
168169
path = LndMobileCallbacks;
169170
sourceTree = "<group>";
@@ -206,7 +207,7 @@
206207
);
207208
name = LightningKit;
208209
packageProductDependencies = (
209-
D3A733C923FA80A2005DAC30 /* GRPC */,
210+
D3A733C923FA80A2005DAC30 /* XCSwiftPackageProductDependency */,
210211
);
211212
productName = LightningKit;
212213
productReference = D3A7338F23FA7D94005DAC30 /* LightningKit.framework */;
@@ -237,7 +238,7 @@
237238
);
238239
mainGroup = D3A7338523FA7D94005DAC30;
239240
packageReferences = (
240-
D3A733C523FA7FD1005DAC30 /* XCRemoteSwiftPackageReference "grpc-swift" */,
241+
D3A733C523FA7FD1005DAC30 /* XCRemoteSwiftPackageReference */,
241242
);
242243
productRefGroup = D3A7339023FA7D94005DAC30 /* Products */;
243244
projectDirPath = "";
@@ -306,6 +307,7 @@
306307
D3A733B523FA7E98005DAC30 /* LocalNodeCredentials.swift in Sources */,
307308
D3A733B623FA7E98005DAC30 /* NodeStatus.swift in Sources */,
308309
D3A733BD23FA7E98005DAC30 /* Kit.swift in Sources */,
310+
2FA5DC8A66918200D42EA1EE /* MessageResponseStream.swift in Sources */,
309311
);
310312
runOnlyForDeploymentPostprocessing = 0;
311313
};
@@ -521,7 +523,7 @@
521523
/* End XCConfigurationList section */
522524

523525
/* Begin XCRemoteSwiftPackageReference section */
524-
D3A733C523FA7FD1005DAC30 /* XCRemoteSwiftPackageReference "grpc-swift" */ = {
526+
D3A733C523FA7FD1005DAC30 /* XCRemoteSwiftPackageReference */ = {
525527
isa = XCRemoteSwiftPackageReference;
526528
repositoryURL = "git@github.com:grpc/grpc-swift.git";
527529
requirement = {
@@ -532,9 +534,9 @@
532534
/* End XCRemoteSwiftPackageReference section */
533535

534536
/* Begin XCSwiftPackageProductDependency section */
535-
D3A733C923FA80A2005DAC30 /* GRPC */ = {
537+
D3A733C923FA80A2005DAC30 /* XCSwiftPackageProductDependency */ = {
536538
isa = XCSwiftPackageProductDependency;
537-
package = D3A733C523FA7FD1005DAC30 /* XCRemoteSwiftPackageReference "grpc-swift" */;
539+
package = D3A733C523FA7FD1005DAC30 /* XCRemoteSwiftPackageReference */;
538540
productName = GRPC;
539541
};
540542
/* End XCSwiftPackageProductDependency section */

LightningKit/ILndNode.swift

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@ protocol ILndNode {
1717
var closedChannelsSingle: Single<Lnrpc_ClosedChannelsResponse> { get }
1818
var pendingChannelsSingle: Single<Lnrpc_PendingChannelsResponse> { get }
1919
var paymentsSingle: Single<Lnrpc_ListPaymentsResponse> { get }
20+
var transactionsSingle: Single<Lnrpc_TransactionDetails> { get }
2021

21-
func invoicesSingle(pendingOnly: Bool, offset: UInt64, limit: UInt64, reversed: Bool) -> Single<Lnrpc_ListInvoiceResponse>
22-
func paySingle(invoice: String) -> Single<Lnrpc_SendResponse>
23-
func addInvoiceSingle(amount: Int64, memo: String) -> Single<Lnrpc_AddInvoiceResponse>
24-
func unlockWalletSingle(password: Data) -> Single<Void>
25-
func decodeSingle(paymentRequest: String) -> Single<Lnrpc_PayReq>
26-
func openChannelSingle(nodePubKey: Data, amount: Int64) -> Observable<Lnrpc_OpenStatusUpdate>
27-
func closeChannelSingle(channelPoint: String, forceClose: Bool) throws -> Observable<Lnrpc_CloseStatusUpdate>
28-
func connectSingle(nodeAddress: String, nodePubKey: String) -> Single<Lnrpc_ConnectPeerResponse>
22+
func invoicesSingle(request: Lnrpc_ListInvoiceRequest) -> Single<Lnrpc_ListInvoiceResponse>
23+
func paySingle(request: Lnrpc_SendRequest) -> Single<Lnrpc_SendResponse>
24+
func addInvoiceSingle(invoice: Lnrpc_Invoice) -> Single<Lnrpc_AddInvoiceResponse>
25+
func unlockWalletSingle(request: Lnrpc_UnlockWalletRequest) -> Single<Void>
26+
func decodeSingle(paymentRequest: Lnrpc_PayReqString) -> Single<Lnrpc_PayReq>
27+
func openChannelSingle(request: Lnrpc_OpenChannelRequest) -> Observable<Lnrpc_OpenStatusUpdate>
28+
func closeChannelSingle(request: Lnrpc_CloseChannelRequest) throws -> Observable<Lnrpc_CloseStatusUpdate>
29+
func connectSingle(request: Lnrpc_ConnectPeerRequest) -> Single<Lnrpc_ConnectPeerResponse>
2930
}

LightningKit/Kit.swift

Lines changed: 134 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import GRPC
33
import RxSwift
44

55
public class Kit {
6+
enum ArgumentError: Error {
7+
case wrongChannelPoint
8+
}
9+
610
private let lndNode: ILndNode
711
private let paymentsUpdatedSubject = PublishSubject<Void>()
812

@@ -28,19 +32,23 @@ public class Kit {
2832
}
2933
public var channelBalanceObservable: Observable<Lnrpc_ChannelBalanceResponse> {
3034
Observable
31-
.merge([
32-
paymentsObservable,
33-
invoicesObservable.filter { $0.state == .settled }.map { _ in Void() }
34-
])
35-
.flatMap { [weak self] _ in
36-
self?.channelBalanceSingle.asObservable() ?? Observable.empty()
37-
}
35+
.merge([
36+
paymentsObservable,
37+
invoicesObservable.filter {
38+
$0.state == .settled
39+
}.map { _ in
40+
Void()
41+
}
42+
])
43+
.flatMap { [weak self] _ in
44+
self?.channelBalanceSingle.asObservable() ?? Observable.empty()
45+
}
3846
}
39-
47+
4048
fileprivate init(lndNode: ILndNode) {
4149
self.lndNode = lndNode
4250
}
43-
51+
4452
public var walletBalanceSingle: Single<Lnrpc_WalletBalanceResponse> {
4553
lndNode.walletBalanceSingle
4654
}
@@ -64,96 +72,175 @@ public class Kit {
6472
public var pendingChannelsSingle: Single<Lnrpc_PendingChannelsResponse> {
6573
lndNode.pendingChannelsSingle
6674
}
67-
75+
6876
public var paymentsSingle: Single<Lnrpc_ListPaymentsResponse> {
6977
lndNode.paymentsSingle
7078
}
79+
80+
public var transactionsSingle: Single<Lnrpc_TransactionDetails> {
81+
lndNode.transactionsSingle
82+
}
83+
84+
public func invoicesSingle(pendingOnly: Bool = false, offset: UInt64 = 0, limit: UInt64 = 1000, reversed: Bool = false) -> Single<Lnrpc_ListInvoiceResponse> {
85+
var request = Lnrpc_ListInvoiceRequest()
86+
request.pendingOnly = pendingOnly
87+
request.indexOffset = offset
88+
request.numMaxInvoices = limit
89+
request.reversed = reversed
7190

72-
public func invoicesSingle(pending_only: Bool = false, offset: UInt64 = 0, limit: UInt64 = 1000, reversed: Bool = false) -> Single<Lnrpc_ListInvoiceResponse> {
73-
lndNode.invoicesSingle(pendingOnly: pending_only, offset: offset, limit: limit, reversed: reversed)
91+
return lndNode.invoicesSingle(request: request)
7492
}
75-
93+
7694
public func paySingle(invoice: String) -> Single<Lnrpc_SendResponse> {
77-
return lndNode.paySingle(invoice: invoice)
78-
.do(onSuccess: { [weak self] in
79-
if $0.paymentError.isEmpty {
80-
self?.paymentsUpdatedSubject.onNext(Void())
81-
}
82-
})
95+
var request = Lnrpc_SendRequest()
96+
request.paymentRequest = invoice
97+
98+
return lndNode.paySingle(request: request)
99+
.do(onSuccess: { [weak self] in
100+
if $0.paymentError.isEmpty {
101+
self?.paymentsUpdatedSubject.onNext(Void())
102+
}
103+
})
83104
}
84105

85106
public func addInvoiceSingle(amount: Int64, memo: String) -> Single<Lnrpc_AddInvoiceResponse> {
86-
lndNode.addInvoiceSingle(amount: amount, memo: memo)
107+
var invoice = Lnrpc_Invoice()
108+
invoice.value = amount
109+
invoice.memo = memo
110+
111+
return lndNode.addInvoiceSingle(invoice: invoice)
87112
}
88113

89114
public func unlockWalletSingle(password: Data) -> Single<Void> {
90-
lndNode.unlockWalletSingle(password: password)
115+
var request = Lnrpc_UnlockWalletRequest()
116+
request.walletPassword = password
117+
118+
return lndNode.unlockWalletSingle(request: request)
91119
}
92-
120+
93121
public func decodeSingle(paymentRequest: String) -> Single<Lnrpc_PayReq> {
94-
lndNode.decodeSingle(paymentRequest: paymentRequest)
122+
var request = Lnrpc_PayReqString()
123+
request.payReq = paymentRequest
124+
125+
return lndNode.decodeSingle(paymentRequest: request)
95126
}
96127

97128
public func openChannelSingle(nodePubKey: Data, amount: Int64, nodeAddress: String) -> Observable<Lnrpc_OpenStatusUpdate> {
98-
lndNode.connectSingle(nodeAddress: nodeAddress, nodePubKey: nodePubKey.hex)
99-
.map { _ in Void() }
100-
.catchError { error -> Single<Void> in
101-
if let grpcStatus = error as? GRPC.GRPCStatus, let message = grpcStatus.message,
102-
message.contains("already connected to peer") {
103-
return Single.just(Void())
104-
} else {
105-
return Single.error(error)
129+
var openChannelRequest = Lnrpc_OpenChannelRequest()
130+
openChannelRequest.nodePubkey = nodePubKey
131+
openChannelRequest.satPerByte = 2 // todo: extract as param
132+
openChannelRequest.localFundingAmount = amount
133+
134+
var lightningAddress = Lnrpc_LightningAddress()
135+
lightningAddress.pubkey = nodePubKey.hex
136+
lightningAddress.host = nodeAddress
137+
138+
var connectPeersRequest = Lnrpc_ConnectPeerRequest()
139+
connectPeersRequest.addr = lightningAddress
140+
141+
return lndNode.connectSingle(request: connectPeersRequest)
142+
.map { _ in
143+
Void()
106144
}
107-
}
108-
.asObservable()
109-
.flatMap { [weak self] _ -> Observable<Lnrpc_OpenStatusUpdate> in
110-
guard let kit = self else {
111-
return Observable.empty()
145+
.catchError { error -> Single<Void> in
146+
if let grpcStatus = error as? GRPC.GRPCStatus, let message = grpcStatus.message,
147+
message.contains("already connected to peer") {
148+
return Single.just(Void())
149+
} else {
150+
return Single.error(error)
151+
}
152+
}
153+
.asObservable()
154+
.flatMap { [weak self] _ -> Observable<Lnrpc_OpenStatusUpdate> in
155+
guard let kit = self else {
156+
return Observable.empty()
157+
}
158+
159+
return kit.lndNode.openChannelSingle(request: openChannelRequest)
112160
}
113-
114-
return kit.lndNode.openChannelSingle(nodePubKey: nodePubKey, amount: amount)
115-
}
116161
}
117162

118163
public func closeChannelSingle(channelPoint: String, forceClose: Bool) throws -> Observable<Lnrpc_CloseStatusUpdate> {
119-
try lndNode.closeChannelSingle(channelPoint: channelPoint, forceClose: forceClose)
164+
let channelPointParts = channelPoint.split(separator: ":")
165+
166+
guard channelPointParts.count == 0, let outputIndex = UInt32(String(channelPointParts[1])) else {
167+
throw ArgumentError.wrongChannelPoint
168+
}
169+
170+
var channelPoint = Lnrpc_ChannelPoint()
171+
channelPoint.fundingTxidStr = String(channelPointParts[0])
172+
channelPoint.outputIndex = outputIndex
173+
174+
var request = Lnrpc_CloseChannelRequest()
175+
request.channelPoint = channelPoint
176+
request.satPerByte = 2 // todo: extract as param
177+
request.force = forceClose
178+
179+
return try lndNode.closeChannelSingle(request: request)
120180
}
121181

122182
private func retryWhenStatusIsSyncingOrRunning<T>(_ observable: Observable<T>) -> Observable<T> {
123183
observable.retryWhen { [weak self] errorObservable -> Observable<(Error, NodeStatus)> in
124184
guard let kit = self else {
125185
return .empty()
126186
}
127-
128-
return Observable.zip(errorObservable, kit.statusObservable.filter { $0 == .syncing || $0 == .running })
187+
188+
return Observable.zip(errorObservable, kit.statusObservable.filter {
189+
$0 == .syncing || $0 == .running
190+
})
129191
}
130192
}
131193
}
132194

133195
public extension Kit {
134196
static var lightningKitLocalLnd: Kit? = nil
135-
197+
136198
static func validateRemoteConnection(rpcCredentials: RpcCredentials) -> Single<Void> {
137199
do {
138200
let remoteLndNode = try RemoteLnd(rpcCredentials: rpcCredentials)
139-
201+
140202
return remoteLndNode.validateAsync()
141203
} catch {
142204
return Single.error(error)
143205
}
144206
}
145207

208+
static func local(credentials: LocalNodeCredentials) -> Kit {
209+
if let kit = lightningKitLocalLnd {
210+
return kit
211+
}
212+
213+
let localLnd = LocalLnd(filesDir: credentials.lndDirPath)
214+
localLnd.startAndUnlock(password: credentials.password)
215+
216+
let kit = Kit(lndNode: localLnd)
217+
lightningKitLocalLnd = kit
218+
219+
return kit
220+
}
221+
146222
static func createLocal(credentials: LocalNodeCredentials) -> Single<[String]> {
147-
let localLnd = LocalLnd(credentials: credentials)
223+
let localLnd = LocalLnd(filesDir: credentials.lndDirPath)
148224
lightningKitLocalLnd = Kit(lndNode: localLnd)
149225

150-
return localLnd.start().flatMap { localLnd.createWallet(password: credentials.password) }
226+
return localLnd.start().flatMap {
227+
localLnd.createWalletSingle(password: credentials.password)
228+
}
229+
}
230+
231+
static func restoreLocal(words: [String], credentials: LocalNodeCredentials) -> Single<Void> {
232+
let localLnd = LocalLnd(filesDir: credentials.lndDirPath)
233+
lightningKitLocalLnd = Kit(lndNode: localLnd)
234+
235+
return localLnd.start().flatMap {
236+
localLnd.restoreWalletSingle(words: words, password: credentials.password)
237+
}
151238
}
152239

153240
static func remote(rpcCredentials: RpcCredentials) throws -> Kit {
154241
let remoteLndNode = try RemoteLnd(rpcCredentials: rpcCredentials)
155242
remoteLndNode.scheduleStatusUpdates()
156-
243+
157244
return Kit(lndNode: remoteLndNode)
158245
}
159246
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
enum LndMobileCallbackError: Error {
22
case unknownError
33
case responseCannotBeDecoded
4+
case nilResponse
45
}

0 commit comments

Comments
 (0)