Skip to content

Commit 585daae

Browse files
committed
Add methods to check if LND is down and which service LND is serving.
1 parent 4a7e962 commit 585daae

File tree

3 files changed

+246
-4
lines changed

3 files changed

+246
-4
lines changed

src/client.js

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const grpc = require("grpc");
77
const protoLoader = require("@grpc/proto-loader");
88
const Long = require("long");
99
const lnPayReq = require("bolt11");
10+
const CONNECTION_ERROR = 'Connection Failed: Check if LND is running and ' +
11+
'gRPC is listening on the correct port.';
1012
class LightningRpc {
1113
constructor(tlsCert, macaroonHex, domainPort) {
1214
this.__domainPort = domainPort || '127.0.0.1:10009';
@@ -22,6 +24,7 @@ class LightningRpc {
2224
this.__mainRpc = null;
2325
this.__unlockerRpc = new this.__lnrpc.WalletUnlocker(this.__domainPort, this.__credentials);
2426
}
27+
// static methods
2528
static fromStrings(tlsCert, macaroonHex, domainPort) {
2629
return new LightningRpc(tlsCert.replace(/[\r\n]/g, ''), macaroonHex, domainPort);
2730
}
@@ -30,26 +33,123 @@ class LightningRpc {
3033
const macaroonHex = fs.readFileSync(macaroonPath).toString('hex');
3134
return new LightningRpc(tlsCert, macaroonHex, domainPort);
3235
}
36+
// private methods
37+
isMain() {
38+
return this.__unlockerRpc === null && this.__mainRpc !== null;
39+
}
40+
isUnlocker() {
41+
return this.__mainRpc === null && this.__unlockerRpc !== null;
42+
}
43+
async isServerDownMain() {
44+
return this.getInfo().then(() => false, err => {
45+
switch (err.code) {
46+
// Error: 14 UNAVAILABLE: Connect Failed
47+
// RPC server is not listening. Fail.
48+
case 14:
49+
return true;
50+
default:
51+
return false;
52+
}
53+
});
54+
}
55+
async isServerDownUnlocker() {
56+
return genSeed(this.__unlockerRpc).then(() => false, err => {
57+
switch (err.code) {
58+
// Error: 14 UNAVAILABLE: Connect Failed
59+
// RPC server is not listening. Fail.
60+
case 14:
61+
return true;
62+
default:
63+
return false;
64+
}
65+
});
66+
}
67+
async hasServiceMain() {
68+
return this.getInfo().then(() => 1, err => {
69+
switch (err.code) {
70+
// Error: 12 UNIMPLEMENTED: unknown service lnrpc.Lightning
71+
case 12:
72+
return 0;
73+
// Error: 14 UNAVAILABLE: Connect Failed
74+
case 14:
75+
return -1; // Throw Error
76+
default:
77+
return 1;
78+
}
79+
});
80+
}
81+
async hasServiceUnlocker() {
82+
return genSeed(this.__unlockerRpc).then(() => 1, err => {
83+
switch (err.code) {
84+
// Error: 12 UNIMPLEMENTED: unknown service lnrpc.WalletUnlocker
85+
case 12:
86+
return 0;
87+
// Error: 14 UNAVAILABLE: Connect Failed
88+
case 14:
89+
return -1; // Throw Error
90+
default:
91+
return 1;
92+
}
93+
});
94+
}
95+
// public methods
3396
async waitForReady() {
3497
let client = this.__unlockerRpc || this.__mainRpc;
3598
await awaitConnection(client, 40); // 40 retries x 500 ms
3699
}
37100
async toMain() {
38-
if (this.__unlockerRpc === null && this.__mainRpc !== null)
101+
if (this.isMain())
39102
return;
40103
this.__unlockerRpc.close();
41104
this.__unlockerRpc = null;
42105
this.__mainRpc = new this.__lnrpc.Lightning(this.__domainPort, this.__credentials);
43106
await awaitConnection(this.__mainRpc, 40); // 40 retries x 500 ms
44107
}
45108
async toUnlocker() {
46-
if (this.__mainRpc === null && this.__unlockerRpc !== null)
109+
if (this.isUnlocker())
47110
return;
48111
this.__mainRpc.close();
49112
this.__mainRpc = null;
50113
this.__unlockerRpc = new this.__lnrpc.WalletUnlocker(this.__domainPort, this.__credentials);
51114
await awaitConnection(this.__unlockerRpc, 40); // 40 retries x 500 ms
52115
}
116+
async isServerDown() {
117+
if (this.isMain()) {
118+
return this.isServerDownMain();
119+
}
120+
else {
121+
return this.isServerDownUnlocker();
122+
}
123+
}
124+
// This is extremely hacky, but until LND supports the gRPC Server Reflection
125+
// This is really the only way to query which service LND is currently serving
126+
async getRemoteService() {
127+
const startTime = new Date().getTime();
128+
let remoteIsMain;
129+
if (this.isMain()) {
130+
let result = await this.hasServiceMain();
131+
if (result === -1)
132+
throw new Error(CONNECTION_ERROR);
133+
remoteIsMain = result === 1;
134+
}
135+
else {
136+
let result = await this.hasServiceUnlocker();
137+
if (result === -1)
138+
throw new Error(CONNECTION_ERROR);
139+
remoteIsMain = result === 0;
140+
}
141+
const endTime = new Date().getTime();
142+
console.log((endTime - startTime) + ' ms');
143+
return remoteIsMain ? 'main' : 'unlocker';
144+
}
145+
getLocalService() {
146+
if (this.isUnlocker()) {
147+
return 'unlocker';
148+
}
149+
else {
150+
return 'main';
151+
}
152+
}
53153
// WalletUnlocker service helper functions. Used for the gRPC server started at
54154
// boot time for LND. Once a wallet has been unlocked/created/restored, toMain()
55155
// should be called and the Lightning service should be used.
@@ -225,6 +325,12 @@ class LightningRpc {
225325
this.__mainRpc.decodePayReq(opts, this.__meta, promiseFunction(resolve, reject));
226326
});
227327
}
328+
async stopDaemon() {
329+
assert(this.__mainRpc, 'stopDaemon requires toMain()');
330+
return new Promise((resolve, reject) => {
331+
this.__mainRpc.stopDaemon({}, this.__meta, promiseFunction(resolve, reject));
332+
});
333+
}
228334
}
229335
exports.LightningRpc = LightningRpc;
230336
async function awaitConnection(client, maxRetries) {

ts-src/client.ts

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import { RpcResponse, CreateResponse, RestoreResponse,
1313
OpenChannelRequest, SendRequest, PaymentHash, PayReqString } from './types'
1414
export * from './types'
1515

16+
const CONNECTION_ERROR = 'Connection Failed: Check if LND is running and ' +
17+
'gRPC is listening on the correct port.'
18+
1619
export class LightningRpc {
1720
private __meta: grpc.Metadata
1821
private __mainRpc: any
@@ -41,6 +44,7 @@ export class LightningRpc {
4144
this.__unlockerRpc = new this.__lnrpc.WalletUnlocker(this.__domainPort, this.__credentials)
4245
}
4346

47+
// static methods
4448
static fromStrings (tlsCert: string, macaroonHex: string, domainPort?: string): LightningRpc {
4549
return new LightningRpc(tlsCert.replace(/[\r\n]/g, ''), macaroonHex, domainPort)
4650
}
@@ -51,27 +55,142 @@ export class LightningRpc {
5155
return new LightningRpc(tlsCert, macaroonHex, domainPort)
5256
}
5357

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
54136
async waitForReady(): Promise<void> {
55137
let client: any = this.__unlockerRpc || this.__mainRpc
56138
await awaitConnection(client, 40) // 40 retries x 500 ms
57139
}
58140

59141
async toMain(): Promise<void> {
60-
if (this.__unlockerRpc === null && this.__mainRpc !== null) return
142+
if (this.isMain()) return
61143
this.__unlockerRpc.close()
62144
this.__unlockerRpc = null
63145
this.__mainRpc = new this.__lnrpc.Lightning(this.__domainPort, this.__credentials)
64146
await awaitConnection(this.__mainRpc, 40) // 40 retries x 500 ms
65147
}
66148

67149
async toUnlocker(): Promise<void> {
68-
if (this.__mainRpc === null && this.__unlockerRpc !== null) return
150+
if (this.isUnlocker()) return
69151
this.__mainRpc.close()
70152
this.__mainRpc = null
71153
this.__unlockerRpc = new this.__lnrpc.WalletUnlocker(this.__domainPort, this.__credentials)
72154
await awaitConnection(this.__unlockerRpc, 40) // 40 retries x 500 ms
73155
}
74156

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+
75194
// WalletUnlocker service helper functions. Used for the gRPC server started at
76195
// boot time for LND. Once a wallet has been unlocked/created/restored, toMain()
77196
// should be called and the Lightning service should be used.
@@ -270,6 +389,13 @@ export class LightningRpc {
270389
this.__mainRpc.decodePayReq(opts, this.__meta, promiseFunction(resolve, reject))
271390
})
272391
}
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+
}
273399
}
274400

275401
async function awaitConnection (client: any, maxRetries: number): Promise<void> {

types/client.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,18 @@ export declare class LightningRpc {
1010
constructor(tlsCert: string, macaroonHex: string, domainPort?: string);
1111
static fromStrings(tlsCert: string, macaroonHex: string, domainPort?: string): LightningRpc;
1212
static fromFilePaths(tlsCertPath: string, macaroonPath: string, domainPort?: string): LightningRpc;
13+
private isMain;
14+
private isUnlocker;
15+
private isServerDownMain;
16+
private isServerDownUnlocker;
17+
private hasServiceMain;
18+
private hasServiceUnlocker;
1319
waitForReady(): Promise<void>;
1420
toMain(): Promise<void>;
1521
toUnlocker(): Promise<void>;
22+
isServerDown(): Promise<boolean>;
23+
getRemoteService(): Promise<string>;
24+
getLocalService(): string;
1625
create(walletPw: string, aezeedPw?: string): Promise<CreateResponse>;
1726
restore(aezeedStr: string, walletPw: string, aezeedPw?: string): Promise<RestoreResponse>;
1827
unlock(password: string): Promise<UnlockResponse>;
@@ -32,4 +41,5 @@ export declare class LightningRpc {
3241
addInvoice(opts: Invoice): Promise<RequestResponse>;
3342
lookupInvoice(opts: PaymentHash): Promise<Invoice>;
3443
decodePayReq(opts: PayReqString): Promise<PayReq>;
44+
stopDaemon(): Promise<void>;
3545
}

0 commit comments

Comments
 (0)