Skip to content

Commit a74780c

Browse files
committed
feat: wait for lnd on create call
This makes the `CreateNode` call wait briefly for all enabled lnd clients to become available. If any lnd clients remain unavailable, the call returns an error. Closes #1252.
1 parent 9913bda commit a74780c

File tree

7 files changed

+100
-48
lines changed

7 files changed

+100
-48
lines changed

lib/grpc/getGrpcError.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ const getGrpcError = (err: any) => {
6464
case p2pErrorCodes.RESPONSE_TIMEOUT:
6565
code = status.DEADLINE_EXCEEDED;
6666
break;
67+
case swapErrorCodes.SWAP_CLIENT_WALLET_NOT_CREATED:
68+
code = status.INTERNAL;
6769
break;
6870
}
6971

lib/lndclient/LndClient.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ class LndClient extends SwapClient {
320320
this.walletUnlocker = new WalletUnlockerClient(this.uri, this.credentials);
321321
await LndClient.waitForClientReady(this.walletUnlocker);
322322
await this.setStatus(ClientStatus.WaitingUnlock);
323+
this.emit('locked');
323324

324325
if (this.reconnectionTimer) {
325326
// we don't need scheduled attempts to retry the connection while waiting on the wallet
@@ -383,7 +384,7 @@ class LndClient extends SwapClient {
383384
}
384385
} else {
385386
await this.setStatus(ClientStatus.OutOfSync);
386-
this.logger.warn(`lnd is out of sync with chain, retrying in ${LndClient.RECONNECT_TIMER} ms`);
387+
this.logger.warn(`lnd is out of sync with chain, retrying in ${LndClient.RECONNECT_TIME_LIMIT} ms`);
387388
}
388389
} catch (err) {
389390
if (err.code === grpc.status.UNIMPLEMENTED) {
@@ -398,7 +399,7 @@ class LndClient extends SwapClient {
398399
}
399400
} else {
400401
const errStr = typeof(err) === 'string' ? err : JSON.stringify(err);
401-
this.logger.error(`could not verify connection at ${this.uri}, error: ${errStr}, retrying in ${LndClient.RECONNECT_TIMER} ms`);
402+
this.logger.error(`could not verify connection at ${this.uri}, error: ${errStr}, retrying in ${LndClient.RECONNECT_TIME_LIMIT} ms`);
402403
await this.disconnect();
403404
}
404405
}

lib/raidenclient/RaidenClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ class RaidenClient extends SwapClient {
150150
await this.setStatus(ClientStatus.ConnectionVerified);
151151
} catch (err) {
152152
this.logger.error(
153-
`could not verify connection to raiden at ${this.host}:${this.port}, retrying in ${RaidenClient.RECONNECT_TIMER} ms`,
153+
`could not verify connection to raiden at ${this.host}:${this.port}, retrying in ${RaidenClient.RECONNECT_TIME_LIMIT} ms`,
154154
err,
155155
);
156156
await this.disconnect();

lib/service/InitService.ts

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,31 +31,29 @@ class InitService extends EventEmitter {
3131
}
3232

3333
this.pendingCall = true;
34+
// wait briefly for all lnd instances to be available
35+
await this.swapClientManager.waitForLnd();
36+
3437
const seed = await this.swapClientManager.genSeed();
3538
let initializedLndWallets: string[] | undefined;
3639
let initializedRaiden = false;
3740
let nodeKey: NodeKey;
3841

39-
if (seed) {
40-
const seedBytes = typeof seed.encipheredSeed === 'string' ?
41-
Buffer.from(seed.encipheredSeed, 'base64') :
42-
Buffer.from(seed.encipheredSeed);
43-
assert.equal(seedBytes.length, 33);
42+
const seedBytes = typeof seed.encipheredSeed === 'string' ?
43+
Buffer.from(seed.encipheredSeed, 'base64') :
44+
Buffer.from(seed.encipheredSeed);
45+
assert.equal(seedBytes.length, 33);
4446

45-
// the seed is 33 bytes, the first byte of which is the version
46-
// so we use the remaining 32 bytes to generate our private key
47-
// TODO: use seedutil tool to derive a child private key from deciphered seed key?
48-
const privKey = Buffer.from(seedBytes.slice(1));
49-
nodeKey = new NodeKey(privKey);
47+
// the seed is 33 bytes, the first byte of which is the version
48+
// so we use the remaining 32 bytes to generate our private key
49+
// TODO: use seedutil tool to derive a child private key from deciphered seed key?
50+
const privKey = Buffer.from(seedBytes.slice(1));
51+
nodeKey = new NodeKey(privKey);
5052

51-
// use this seed to init any lnd wallets that are uninitialized
52-
const initWalletResult = await this.swapClientManager.initWallets(password, seed.cipherSeedMnemonicList);
53-
initializedLndWallets = initWalletResult.initializedLndWallets;
54-
initializedRaiden = initWalletResult.initializedRaiden;
55-
} else {
56-
// we couldn't generate a seed externally, so we must create a nodekey from scratch
57-
nodeKey = await NodeKey.generate();
58-
}
53+
// use this seed to init any lnd wallets that are uninitialized
54+
const initWalletResult = await this.swapClientManager.initWallets(password, seed.cipherSeedMnemonicList);
55+
initializedLndWallets = initWalletResult.initializedLndWallets;
56+
initializedRaiden = initWalletResult.initializedRaiden;
5957

6058
await nodeKey.toFile(this.nodeKeyPath, password);
6159
this.emit('nodekey', nodeKey);

lib/swaps/SwapClient.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ abstract class SwapClient extends EventEmitter {
4949
*/
5050
public abstract readonly finalLock: number;
5151
public abstract readonly type: SwapClientType;
52+
/** Time in milliseconds between attempts to recheck connectivity to the client. */
53+
public static readonly RECONNECT_TIME_LIMIT = 5000;
5254
protected status: ClientStatus = ClientStatus.NotInitialized;
5355
protected reconnectionTimer?: NodeJS.Timer;
54-
/** Time in milliseconds between attempts to recheck connectivity to the client. */
55-
protected static readonly RECONNECT_TIMER = 5000;
5656

5757
private updateCapacityTimer?: NodeJS.Timer;
5858
/** The maximum amount of time we will wait for the connection to be verified during initialization. */
@@ -126,7 +126,7 @@ abstract class SwapClient extends EventEmitter {
126126
// if we were still not able to verify the connection, schedule another attempt
127127
this.reconnectionTimer.refresh();
128128
}
129-
}, SwapClient.RECONNECT_TIMER);
129+
}, SwapClient.RECONNECT_TIME_LIMIT);
130130
}
131131
}
132132
}

lib/swaps/SwapClientManager.ts

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1+
import { EventEmitter } from 'events';
12
import Config from '../Config';
2-
import SwapClient from './SwapClient';
3+
import { SwapClientType } from '../constants/enums';
4+
import { Models } from '../db/DB';
35
import LndClient from '../lndclient/LndClient';
46
import { LndInfo } from '../lndclient/types';
5-
import RaidenClient from '../raidenclient/RaidenClient';
67
import { Loggers } from '../Logger';
78
import { Currency } from '../orderbook/types';
8-
import { Models } from '../db/DB';
9-
import { SwapClientType } from '../constants/enums';
10-
import { EventEmitter } from 'events';
119
import Peer from '../p2p/Peer';
12-
import { UnitConverter } from '../utils/UnitConverter';
10+
import RaidenClient from '../raidenclient/RaidenClient';
1311
import seedutil from '../utils/seedutil';
12+
import { UnitConverter } from '../utils/UnitConverter';
1413
import errors from './errors';
14+
import SwapClient, { ClientStatus } from './SwapClient';
15+
import lndErrors from '../lndclient/errors';
1516

1617
export function isRaidenClient(swapClient: SwapClient): swapClient is RaidenClient {
1718
return (swapClient.type === SwapClientType.Raiden);
@@ -38,7 +39,7 @@ interface SwapClientManager {
3839
}
3940

4041
class SwapClientManager extends EventEmitter {
41-
/** A map between currencies and all swap clients */
42+
/** A map between currencies and all enabled swap clients */
4243
public swapClients = new Map<string, SwapClient>();
4344
public raidenClient: RaidenClient;
4445
private walletPassword?: string;
@@ -105,24 +106,63 @@ class SwapClientManager extends EventEmitter {
105106
}
106107
}
107108

109+
/**
110+
* Checks to make sure all enabled lnd instances are available and waits
111+
* up to a short time for them to become available in case they are not.
112+
* Throws an error if any lnd clients remain unreachable.
113+
*/
114+
public waitForLnd = async () => {
115+
const lndClients = this.getLndClientsMap().values();
116+
const lndAvailablePromises: Promise<void>[] = [];
117+
for (const lndClient of lndClients) {
118+
lndAvailablePromises.push(new Promise<void>((resolve, reject) => {
119+
if (lndClient.isDisconnected() || lndClient.isNotInitialized()) {
120+
const onAvailable = () => {
121+
clearTimeout(timer);
122+
lndClient.removeListener('locked', onAvailable);
123+
lndClient.removeListener('connectionVerified', onAvailable);
124+
resolve();
125+
};
126+
lndClient.once('connectionVerified', onAvailable);
127+
lndClient.once('locked', onAvailable);
128+
const timer = setTimeout(() => {
129+
lndClient.removeListener('connectionVerified', onAvailable);
130+
lndClient.removeListener('locked', onAvailable);
131+
reject(lndClient.currency);
132+
}, SwapClient.RECONNECT_TIME_LIMIT);
133+
} else {
134+
resolve();
135+
}
136+
}));
137+
}
138+
139+
try {
140+
await Promise.all(lndAvailablePromises);
141+
} catch (currency) {
142+
throw lndErrors.UNAVAILABLE(currency, ClientStatus.Disconnected);
143+
}
144+
}
145+
108146
/**
109147
* Generates a cryptographically random 24 word seed mnemonic from an lnd client.
110148
*/
111149
public genSeed = async () => {
150+
const lndClients = this.getLndClientsMap().values();
112151
// loop through swap clients until we find an lnd client awaiting unlock
113-
for (const swapClient of this.swapClients.values()) {
114-
if (isLndClient(swapClient) && swapClient.isWaitingUnlock()) {
152+
for (const lndClient of lndClients) {
153+
if (lndClient.isWaitingUnlock()) {
115154
try {
116-
const seed = await swapClient.genSeed();
155+
const seed = await lndClient.genSeed();
117156
return seed;
118157
} catch (err) {
119-
swapClient.logger.error('could not generate seed', err);
158+
lndClient.logger.error('could not generate seed', err);
120159
}
121160
}
122161
}
123162

124-
// TODO: use seedutil tool to generate a seed
125-
return undefined;
163+
// TODO: use seedutil tool to generate a seed instead of throwing error
164+
// when we can't generate one with lnd
165+
throw errors.SWAP_CLIENT_WALLET_NOT_CREATED('could not generate aezeed');
126166
}
127167

128168
/**
@@ -131,18 +171,23 @@ class SwapClientManager extends EventEmitter {
131171
public initWallets = async (walletPassword: string, seedMnemonic: string[]) => {
132172
this.walletPassword = walletPassword;
133173

134-
// loop through swap clients to find locked lnd clients
174+
// loop through swap clients to initialize locked lnd clients
175+
const lndClients = this.getLndClientsMap().values();
135176
const initWalletPromises: Promise<any>[] = [];
136177
const initializedLndWallets: string[] = [];
137178
let initializedRaiden = false;
138-
for (const swapClient of this.swapClients.values()) {
139-
if (isLndClient(swapClient) && swapClient.isWaitingUnlock()) {
140-
const initWalletPromise = swapClient.initWallet(walletPassword, seedMnemonic).then(() => {
141-
initializedLndWallets.push(swapClient.currency);
142-
}).catch((err) => {
143-
swapClient.logger.debug(`could not initialize wallet: ${err.message}`);
144-
});
145-
initWalletPromises.push(initWalletPromise);
179+
180+
for (const lndClient of lndClients) {
181+
if (isLndClient(lndClient)) {
182+
if (lndClient.isWaitingUnlock()) {
183+
const initWalletPromise = lndClient.initWallet(walletPassword, seedMnemonic).then(() => {
184+
initializedLndWallets.push(lndClient.currency);
185+
}).catch((err) => {
186+
lndClient.logger.error(`could not initialize wallet: ${err.message}`);
187+
throw errors.SWAP_CLIENT_WALLET_NOT_CREATED(`could not initialize lnd-${lndClient.currency}: ${err.message}`);
188+
});
189+
initWalletPromises.push(initWalletPromise);
190+
}
146191
}
147192
}
148193

@@ -154,8 +199,9 @@ class SwapClientManager extends EventEmitter {
154199
const keystorePromise = seedutil(seedMnemonic, '', keystorepath).then(() => {
155200
this.raidenClient.logger.info(`created raiden keystore with master seed and empty password in ${keystorepath}`);
156201
initializedRaiden = true;
157-
}).catch(() => {
158-
this.raidenClient.logger.warn('could not create keystore');
202+
}).catch((err) => {
203+
this.raidenClient.logger.error('could not create keystore');
204+
throw errors.SWAP_CLIENT_WALLET_NOT_CREATED(`could not create keystore: ${err}`);
159205
});
160206
initWalletPromises.push(keystorePromise);
161207
}

lib/swaps/errors.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const errorCodes = {
88
PAYMENT_ERROR: codesPrefix.concat('.4'),
99
PAYMENT_REJECTED: codesPrefix.concat('.5'),
1010
INVALID_RESOLVE_REQUEST: codesPrefix.concat('.6'),
11+
SWAP_CLIENT_WALLET_NOT_CREATED: codesPrefix.concat('.7'),
1112
};
1213

1314
const errors = {
@@ -35,6 +36,10 @@ const errors = {
3536
message: `invalid resolve request for rHash ${rHash}: ${errorMessage}`,
3637
code: errorCodes.INVALID_RESOLVE_REQUEST,
3738
}),
39+
SWAP_CLIENT_WALLET_NOT_CREATED: (message: string) => ({
40+
message,
41+
code: errorCodes.SWAP_CLIENT_WALLET_NOT_CREATED,
42+
}),
3843
};
3944

4045
export { errorCodes };

0 commit comments

Comments
 (0)