Skip to content

Commit 931d8e8

Browse files
committed
feat(rpc): notify when xud is starting
This changes the xud initialization procedure to start the grpc server immediately and have it return an `UNAVAILABLE` error with the message 'xud is starting' until all components have finished initializing and xud is ready to handle rpc calls. It also enhances the xucli output somewhat in cases where xud is locked or starting. Closes #1258.
1 parent a593288 commit 931d8e8

File tree

7 files changed

+214
-65
lines changed

7 files changed

+214
-65
lines changed

Diff for: lib/Xud.ts

+42-38
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1+
import { EventEmitter } from 'events';
2+
import { promises as fs } from 'fs';
13
import path from 'path';
24
import bootstrap from './bootstrap';
3-
import Logger from './Logger';
45
import Config from './Config';
56
import DB from './db/DB';
6-
import OrderBook from './orderbook/OrderBook';
77
import GrpcServer from './grpc/GrpcServer';
88
import GrpcWebProxyServer from './grpc/webproxy/GrpcWebProxyServer';
9-
import Pool from './p2p/Pool';
9+
import HttpServer from './http/HttpServer';
10+
import Logger from './Logger';
1011
import NodeKey from './nodekey/NodeKey';
12+
import OrderBook from './orderbook/OrderBook';
13+
import Pool from './p2p/Pool';
14+
import InitService from './service/InitService';
1115
import Service from './service/Service';
12-
import { EventEmitter } from 'events';
13-
import Swaps from './swaps/Swaps';
14-
import HttpServer from './http/HttpServer';
1516
import SwapClientManager from './swaps/SwapClientManager';
16-
import InitService from './service/InitService';
17-
import { promises as fs } from 'fs';
17+
import Swaps from './swaps/Swaps';
1818
import { UnitConverter } from './utils/UnitConverter';
1919

2020
const version: string = require('../package.json').version;
@@ -72,6 +72,28 @@ class Xud extends EventEmitter {
7272
}
7373

7474
try {
75+
if (!this.config.rpc.disable) {
76+
// start rpc server first, it will respond with UNAVAILABLE error
77+
// indicating xud is starting until xud finishes initializing
78+
this.rpcServer = new GrpcServer(loggers.rpc);
79+
await this.rpcServer.listen(
80+
this.config.rpc.port,
81+
this.config.rpc.host,
82+
path.join(this.config.xudir, 'tls.cert'),
83+
path.join(this.config.xudir, 'tls.key'),
84+
);
85+
86+
if (!this.config.webproxy.disable) {
87+
this.grpcAPIProxy = new GrpcWebProxyServer(loggers.rpc);
88+
await this.grpcAPIProxy.listen(
89+
this.config.webproxy.port,
90+
this.config.rpc.port,
91+
this.config.rpc.host,
92+
path.join(this.config.xudir, 'tls.cert'),
93+
);
94+
}
95+
}
96+
7597
this.db = new DB(loggers.db, this.config.dbpath);
7698
await this.db.init(this.config.network, this.config.initdb);
7799

@@ -93,18 +115,11 @@ class Xud extends EventEmitter {
93115
nodeKey = await NodeKey.generate();
94116
await nodeKey.toFile(nodeKeyPath);
95117
}
96-
} else {
118+
} else if (this.rpcServer) {
119+
this.rpcServer.grpcService.locked = true;
97120
const initService = new InitService(this.swapClientManager, nodeKeyPath, nodeKeyExists);
98121

99-
const initRpcServer = new GrpcServer(loggers.rpc);
100-
initRpcServer.addXudInitService(initService);
101-
await initRpcServer.listen(
102-
this.config.rpc.port,
103-
this.config.rpc.host,
104-
path.join(this.config.xudir, 'tls.cert'),
105-
path.join(this.config.xudir, 'tls.key'),
106-
);
107-
122+
this.rpcServer.grpcInitService.setInitService(initService);
108123
this.logger.info("Node key is encrypted, unlock using 'xucli unlock' or set password using" +
109124
" 'xucli create' if this is the first time starting xud");
110125
nodeKey = await new Promise<NodeKey | undefined>((resolve) => {
@@ -115,10 +130,15 @@ class Xud extends EventEmitter {
115130
initService.removeListener('nodekey', resolve);
116131
});
117132
});
118-
await initRpcServer.close();
119133
if (!nodeKey) {
120134
return; // we interrupted before unlocking xud
121135
}
136+
this.rpcServer.grpcService.locked = false;
137+
} else {
138+
throw new Error('rpc server cannot be disabled when xud is locked');
139+
}
140+
if (this.rpcServer) {
141+
this.rpcServer.grpcInitService.disabled = true;
122142
}
123143

124144
this.logger.info(`Local nodePubKey is ${nodeKey.pubKey}`);
@@ -172,26 +192,10 @@ class Xud extends EventEmitter {
172192
);
173193
}
174194

175-
// start rpc server last
176-
if (!this.config.rpc.disable) {
177-
this.rpcServer = new GrpcServer(loggers.rpc);
178-
this.rpcServer.addXudService(this.service);
179-
await this.rpcServer.listen(
180-
this.config.rpc.port,
181-
this.config.rpc.host,
182-
path.join(this.config.xudir, 'tls.cert'),
183-
path.join(this.config.xudir, 'tls.key'),
184-
);
195+
// initialize rpc server last
196+
if (this.rpcServer) {
197+
this.rpcServer.grpcService.setService(this.service);
185198

186-
if (!this.config.webproxy.disable) {
187-
this.grpcAPIProxy = new GrpcWebProxyServer(loggers.rpc);
188-
await this.grpcAPIProxy.listen(
189-
this.config.webproxy.port,
190-
this.config.rpc.port,
191-
this.config.rpc.host,
192-
path.join(this.config.xudir, 'tls.cert'),
193-
);
194-
}
195199
} else {
196200
this.logger.info('RPC server is disabled.');
197201
}

Diff for: lib/cli/command.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import fs from 'fs';
2-
import grpc from 'grpc';
2+
import grpc, { status } from 'grpc';
33
import { Arguments } from 'yargs';
44
import { XudClient, XudInitClient } from '../proto/xudrpc_grpc_pb';
55
import { getDefaultCertPath } from './utils';
@@ -29,10 +29,20 @@ interface GrpcResponse {
2929
}
3030

3131
export const callback = (argv: Arguments, formatOutput?: Function, displayJson?: Function) => {
32-
return (error: Error | null, response: GrpcResponse) => {
32+
return (error: grpc.ServiceError | null, response: GrpcResponse) => {
3333
if (error) {
3434
process.exitCode = 1;
35-
console.error(`${error.name}: ${error.message}`);
35+
if (error.code === status.UNAVAILABLE) {
36+
if (error.message.includes('xud is starting')) {
37+
console.error('xud is starting... try again in a few seconds');
38+
} else {
39+
console.error(`could not connect to xud at ${argv.rpchost}:${argv.rpcport}, is xud running?`);
40+
}
41+
} else if (error.code === status.UNIMPLEMENTED && error.message.includes('xud is locked')) {
42+
console.error("xud is locked, run 'xucli unlock' or 'xucli create' then try again");
43+
} else {
44+
console.error(`${error.name}: ${error.message}`);
45+
}
3646
} else {
3747
const responseObj = response.toObject();
3848
if (Object.keys(responseObj).length === 0) {

Diff for: lib/grpc/GrpcInitService.ts

+32-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,43 @@
11
/* tslint:disable no-null-keyword */
2-
import grpc from 'grpc';
2+
import grpc, { status } from 'grpc';
33
import InitService from 'lib/service/InitService';
44
import * as xudrpc from '../proto/xudrpc_pb';
55
import getGrpcError from './getGrpcError';
66

77
class GrpcInitService {
8-
constructor(private initService: InitService) {}
8+
public disabled = false;
9+
private initService?: InitService;
10+
11+
constructor() {}
12+
13+
public setInitService(initService: InitService) {
14+
this.initService = initService;
15+
}
16+
17+
/**
18+
* Checks whether this service is ready to handle calls and sends an error to the client
19+
* caller if not ready.
20+
* @returns `true` if the service is ready, otherwise `false`
21+
*/
22+
private isReady = (initService: InitService | undefined, callback: grpc.sendUnaryData<any>)
23+
: initService is InitService => {
24+
if (!initService) {
25+
const err = this.disabled ?
26+
{ code: status.UNIMPLEMENTED, message: 'xud init service is disabled', name: 'DisabledError' } :
27+
{ code: status.UNAVAILABLE, message: 'xud is starting', name: 'NotReadyError' };
28+
callback(err, null);
29+
return false;
30+
}
31+
return true;
32+
}
933

1034
/**
1135
* See [[InitService.createNode]]
1236
*/
1337
public createNode: grpc.handleUnaryCall<xudrpc.CreateNodeRequest, xudrpc.CreateNodeResponse> = async (call, callback) => {
38+
if (!this.isReady(this.initService, callback)) {
39+
return;
40+
}
1441
try {
1542
const { mnemonic, initializedLndWallets, initializedRaiden } = await this.initService.createNode(call.request.toObject());
1643
const response = new xudrpc.CreateNodeResponse();
@@ -33,6 +60,9 @@ class GrpcInitService {
3360
* See [[InitService.unlockNode]]
3461
*/
3562
public unlockNode: grpc.handleUnaryCall<xudrpc.UnlockNodeRequest, xudrpc.UnlockNodeResponse> = async (call, callback) => {
63+
if (!this.isReady(this.initService, callback)) {
64+
return;
65+
}
3666
try {
3767
const unlockedLndClients = await this.initService.unlockNode(call.request.toObject());
3868
const response = new xudrpc.UnlockNodeResponse();

Diff for: lib/grpc/GrpcServer.ts

+14-20
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
1-
import { hostname } from 'os';
2-
import grpc from 'grpc';
3-
import { pki, md } from 'node-forge';
41
import assert from 'assert';
2+
import { promises as fs } from 'fs';
3+
import grpc from 'grpc';
4+
import { md, pki } from 'node-forge';
5+
import { hostname } from 'os';
56
import Logger from '../Logger';
6-
import GrpcService from './GrpcService';
7-
import Service from '../service/Service';
7+
import { XudInitService, XudService } from '../proto/xudrpc_grpc_pb';
88
import errors from './errors';
9-
import { XudService, XudInitService } from '../proto/xudrpc_grpc_pb';
10-
import { promises as fs } from 'fs';
11-
import serverProxy from './serverProxy';
12-
import InitService from 'lib/service/InitService';
139
import GrpcInitService from './GrpcInitService';
10+
import GrpcService from './GrpcService';
11+
import serverProxy from './serverProxy';
1412

1513
class GrpcServer {
14+
public grpcService = new GrpcService();
15+
public grpcInitService = new GrpcInitService();
1616
private server: any;
17-
private grpcService?: GrpcService;
1817

1918
constructor(private logger: Logger) {
2019
this.server = serverProxy(new grpc.Server());
2120

21+
this.grpcInitService = new GrpcInitService();
22+
this.grpcService = new GrpcService();
23+
this.server.addService(XudInitService, this.grpcInitService);
24+
this.server.addService(XudService, this.grpcService);
25+
2226
this.server.use(async (ctx: any, next: any) => {
2327
logger.debug(`received call ${ctx.service.path}`);
2428

@@ -35,16 +39,6 @@ class GrpcServer {
3539
});
3640
}
3741

38-
public addXudInitService = (initService: InitService) => {
39-
const grpcInitService = new GrpcInitService(initService);
40-
this.server.addService(XudInitService, grpcInitService);
41-
}
42-
43-
public addXudService = (service: Service) => {
44-
this.grpcService = new GrpcService(service);
45-
this.server.addService(XudService, this.grpcService);
46-
}
47-
4842
/**
4943
* Start the server and begin listening on the provided port
5044
* @returns true if the server started listening successfully, false otherwise

0 commit comments

Comments
 (0)