@@ -13,6 +13,9 @@ import { RpcResponse, CreateResponse, RestoreResponse,
13
13
OpenChannelRequest , SendRequest , PaymentHash , PayReqString } from './types'
14
14
export * from './types'
15
15
16
+ const CONNECTION_ERROR = 'Connection Failed: Check if LND is running and ' +
17
+ 'gRPC is listening on the correct port.'
18
+
16
19
export class LightningRpc {
17
20
private __meta : grpc . Metadata
18
21
private __mainRpc : any
@@ -41,6 +44,7 @@ export class LightningRpc {
41
44
this . __unlockerRpc = new this . __lnrpc . WalletUnlocker ( this . __domainPort , this . __credentials )
42
45
}
43
46
47
+ // static methods
44
48
static fromStrings ( tlsCert : string , macaroonHex : string , domainPort ?: string ) : LightningRpc {
45
49
return new LightningRpc ( tlsCert . replace ( / [ \r \n ] / g, '' ) , macaroonHex , domainPort )
46
50
}
@@ -51,27 +55,142 @@ export class LightningRpc {
51
55
return new LightningRpc ( tlsCert , macaroonHex , domainPort )
52
56
}
53
57
58
+ // private methods
59
+ private isMain ( ) : boolean {
60
+ return this . __unlockerRpc === null && this . __mainRpc !== null
61
+ }
62
+
63
+ private isUnlocker ( ) : boolean {
64
+ return this . __mainRpc === null && this . __unlockerRpc !== null
65
+ }
66
+
67
+ private async isServerDownMain ( ) : Promise < boolean > {
68
+ return this . getInfo ( ) . then (
69
+ ( ) => false ,
70
+ err => {
71
+ switch ( err . code ) {
72
+ // Error: 14 UNAVAILABLE: Connect Failed
73
+ // RPC server is not listening. Fail.
74
+ case 14 :
75
+ return true
76
+ default :
77
+ return false
78
+ }
79
+ }
80
+ )
81
+ }
82
+
83
+ private async isServerDownUnlocker ( ) : Promise < boolean > {
84
+ return genSeed ( this . __unlockerRpc ) . then (
85
+ ( ) => false ,
86
+ err => {
87
+ switch ( err . code ) {
88
+ // Error: 14 UNAVAILABLE: Connect Failed
89
+ // RPC server is not listening. Fail.
90
+ case 14 :
91
+ return true
92
+ default :
93
+ return false
94
+ }
95
+ }
96
+ )
97
+ }
98
+
99
+ private async hasServiceMain ( ) : Promise < number > {
100
+ return this . getInfo ( ) . then (
101
+ ( ) => 1 ,
102
+ err => {
103
+ switch ( err . code ) {
104
+ // Error: 12 UNIMPLEMENTED: unknown service lnrpc.Lightning
105
+ case 12 :
106
+ return 0
107
+ // Error: 14 UNAVAILABLE: Connect Failed
108
+ case 14 :
109
+ return - 1 // Throw Error
110
+ default :
111
+ return 1
112
+ }
113
+ }
114
+ )
115
+ }
116
+
117
+ private async hasServiceUnlocker ( ) : Promise < number > {
118
+ return genSeed ( this . __unlockerRpc ) . then (
119
+ ( ) => 1 ,
120
+ err => {
121
+ switch ( err . code ) {
122
+ // Error: 12 UNIMPLEMENTED: unknown service lnrpc.WalletUnlocker
123
+ case 12 :
124
+ return 0
125
+ // Error: 14 UNAVAILABLE: Connect Failed
126
+ case 14 :
127
+ return - 1 // Throw Error
128
+ default :
129
+ return 1
130
+ }
131
+ }
132
+ )
133
+ }
134
+
135
+ // public methods
54
136
async waitForReady ( ) : Promise < void > {
55
137
let client : any = this . __unlockerRpc || this . __mainRpc
56
138
await awaitConnection ( client , 40 ) // 40 retries x 500 ms
57
139
}
58
140
59
141
async toMain ( ) : Promise < void > {
60
- if ( this . __unlockerRpc === null && this . __mainRpc !== null ) return
142
+ if ( this . isMain ( ) ) return
61
143
this . __unlockerRpc . close ( )
62
144
this . __unlockerRpc = null
63
145
this . __mainRpc = new this . __lnrpc . Lightning ( this . __domainPort , this . __credentials )
64
146
await awaitConnection ( this . __mainRpc , 40 ) // 40 retries x 500 ms
65
147
}
66
148
67
149
async toUnlocker ( ) : Promise < void > {
68
- if ( this . __mainRpc === null && this . __unlockerRpc !== null ) return
150
+ if ( this . isUnlocker ( ) ) return
69
151
this . __mainRpc . close ( )
70
152
this . __mainRpc = null
71
153
this . __unlockerRpc = new this . __lnrpc . WalletUnlocker ( this . __domainPort , this . __credentials )
72
154
await awaitConnection ( this . __unlockerRpc , 40 ) // 40 retries x 500 ms
73
155
}
74
156
157
+ async isServerDown ( ) : Promise < boolean > {
158
+ if ( this . isMain ( ) ) {
159
+ return this . isServerDownMain ( )
160
+ } else {
161
+ return this . isServerDownUnlocker ( )
162
+ }
163
+ }
164
+
165
+ // This is extremely hacky, but until LND supports the gRPC Server Reflection
166
+ // This is really the only way to query which service LND is currently serving
167
+ async getRemoteService ( ) : Promise < string > {
168
+ const startTime = new Date ( ) . getTime ( )
169
+
170
+ let remoteIsMain : boolean
171
+ if ( this . isMain ( ) ) {
172
+ let result = await this . hasServiceMain ( )
173
+ if ( result === - 1 ) throw new Error ( CONNECTION_ERROR )
174
+ remoteIsMain = result === 1
175
+ } else {
176
+ let result = await this . hasServiceUnlocker ( )
177
+ if ( result === - 1 ) throw new Error ( CONNECTION_ERROR )
178
+ remoteIsMain = result === 0
179
+ }
180
+
181
+ const endTime = new Date ( ) . getTime ( )
182
+ console . log ( ( endTime - startTime ) + ' ms' )
183
+ return remoteIsMain ? 'main' : 'unlocker'
184
+ }
185
+
186
+ getLocalService ( ) : string {
187
+ if ( this . isUnlocker ( ) ) {
188
+ return 'unlocker'
189
+ } else {
190
+ return 'main'
191
+ }
192
+ }
193
+
75
194
// WalletUnlocker service helper functions. Used for the gRPC server started at
76
195
// boot time for LND. Once a wallet has been unlocked/created/restored, toMain()
77
196
// should be called and the Lightning service should be used.
@@ -270,6 +389,13 @@ export class LightningRpc {
270
389
this . __mainRpc . decodePayReq ( opts , this . __meta , promiseFunction ( resolve , reject ) )
271
390
} )
272
391
}
392
+
393
+ async stopDaemon ( ) : Promise < void > {
394
+ assert ( this . __mainRpc , 'stopDaemon requires toMain()' )
395
+ return new Promise ( ( resolve , reject ) => {
396
+ this . __mainRpc . stopDaemon ( { } , this . __meta , promiseFunction ( resolve , reject ) )
397
+ } )
398
+ }
273
399
}
274
400
275
401
async function awaitConnection ( client : any , maxRetries : number ) : Promise < void > {
0 commit comments