ln-service 59.3.2 → 59.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,558 +0,0 @@
1
- const {join} = require('path');
2
- const {readFile} = require('fs');
3
- const {readFileSync} = require('fs');
4
- const {spawn} = require('child_process');
5
-
6
- const asyncAuto = require('async/auto');
7
- const asyncMap = require('async/map');
8
- const asyncMapSeries = require('async/mapSeries');
9
- const asyncRetry = require('async/retry');
10
- const {networks} = require('bitcoinjs-lib');
11
- const openPortFinder = require('portfinder');
12
- const tinysecp = require('tiny-secp256k1');
13
-
14
- const {authenticatedLndGrpc} = require('./../../');
15
- const {createSeed} = require('./../../');
16
- const {createWallet} = require('./../../');
17
- const generateBlocks = require('./generate_blocks');
18
- const {getWalletInfo} = require('./../../');
19
- const spawnChainDaemon = require('./spawn_chain_daemon');
20
- const {subscribeToWalletStatus} = require('./../../');
21
- const {unauthenticatedLndGrpc} = require('./../../');
22
-
23
- const adminMacaroonFileName = 'admin.macaroon';
24
- const chainPass = '0k39BVOdg4uuS7qNCG2jbIXNpwU7d3Ft87PpHPPoCfk=';
25
- const chainRpcCertName = 'rpc.cert';
26
- const chainUser = 'bitcoinrpc';
27
- const interval = retryCount => 50 * Math.pow(2, retryCount);
28
- const invoiceMacaroonFileName = 'invoice.macaroon';
29
- const {isArray} = Array;
30
- const lightningDaemonExecFileName = 'lnd';
31
- const lightningDaemonLogPath = 'logs/';
32
- const lightningSeedPassphrase = 'passphrase';
33
- const lightningTlsCertFileName = 'tls.cert';
34
- const lightningTlsKeyFileName = 'tls.key';
35
- const lightningWalletPassword = 'password';
36
- const lndWalletUnlockerService = 'Unlocker';
37
- const localhost = '127.0.0.1';
38
- const maxSpawnChainDaemonAttempts = 10;
39
- const {random} = Math;
40
- const readMacaroonFileName = 'readonly.macaroon';
41
- const retryCreateSeedCount = 5;
42
- const {round} = Math;
43
- const startPortRange = 7593;
44
- const startWalletTimeoutMs = 5500;
45
- const times = 30;
46
-
47
- /** Spawn an LND instance
48
-
49
- {
50
- [circular]: <Allow Circular Payments Bool>
51
- [intercept]: <Enable RPC Interception Bool>
52
- [keysend]: <Enable Key Send Bool>
53
- [noauth]: <Disable Macaroon Bool>
54
- [seed]: <Seed Phrase String>
55
- [tower]: <Tower Enabled Bool>
56
- [watchers]: <Watchtower Client Enabled Bool>
57
- }
58
-
59
- @returns via cbk
60
- {
61
- chain_listen_port: <Chain Listen Port Number>
62
- chain_rpc_cert: <RPC Cert Path String>
63
- chain_rpc_pass: <Chain RPC Password String>
64
- chain_rpc_port: <RPC Port Number>
65
- chain_rpc_user: <Chain RPC Username String>
66
- kill: <Stop Function> ({}, err => {})
67
- listen_ip: <Listen Ip String>
68
- listen_port: <Listen Port Number>
69
- lnd: <LND GRPC API Object>
70
- lnd_cert: <LND Base64 Encoded TLS Certificate String>
71
- lnd_macaroon: <LND Base64 Encoded Authentication Macaroon String>
72
- lnd_socket: <LND RPC Socket String>
73
- mining_key: <Mining Rewards Private Key WIF Encoded String>
74
- public_key: <Node Public Key Hex String>
75
- rpc_port: <RPC Port Number>
76
- seed: <Node Seed Phrase String>
77
- socket: <LND RPC Network Socket String>
78
- }
79
- */
80
- module.exports = (args, cbk) => {
81
- return asyncAuto({
82
- // Import ECPair library
83
- ecp: async () => (await import('ecpair')).ECPairFactory(tinysecp),
84
-
85
- // Find open ports for the listen, REST and RPC ports
86
- getPorts: cbk => {
87
- return asyncRetry({interval: n => round(random() * 100), times: 1000}, cbk => {
88
- let i = 0;
89
- const ports = ['listen', 'rest', 'rpc', 'tower'];
90
-
91
- return asyncMapSeries(ports, (_, cbk) => {
92
- const port = startPortRange + (++i * 1000) + round(random() * 1000);
93
-
94
- const stopPort = port + 20000;
95
-
96
- return setTimeout(() => {
97
- return openPortFinder.getPort({port, stopPort}, cbk);
98
- },
99
- round(random() * 100));
100
- },
101
- (err, ports) => {
102
- if (!!err || !isArray(ports) || !ports.length) {
103
- return setTimeout(() => {
104
- return cbk([500, 'FailedToFindOpenPortsWhenSpawningLnd', {err}]);
105
- },
106
- round(random() * 1000));
107
- }
108
-
109
- const [listen, rest, rpc, tower] = ports;
110
-
111
- return cbk(null, {listen, rest, rpc, tower});
112
- });
113
- },
114
- cbk);
115
- },
116
-
117
- // Make a private key for mining rewards
118
- miningKey: ['ecp', ({ecp}, cbk) => {
119
- const keyPair = ecp.makeRandom({network: networks.testnet});
120
-
121
- return cbk(null, {
122
- private_key: keyPair.toWIF(),
123
- public_key: keyPair.publicKey.toString('hex'),
124
- });
125
- }],
126
-
127
- // Spawn a backing chain daemon for lnd
128
- spawnChainDaemon: ['miningKey', ({miningKey}, cbk) => {
129
- return asyncRetry(maxSpawnChainDaemonAttempts, cbk => {
130
- return spawnChainDaemon({
131
- daemon: 'btcd',
132
- is_tls: true,
133
- mining_public_key: miningKey.public_key,
134
- },
135
- cbk);
136
- },
137
- cbk);
138
- }],
139
-
140
- // Get the chain daemon cert
141
- getChainDaemonCert: ['spawnChainDaemon', ({spawnChainDaemon}, cbk) => {
142
- return asyncRetry({interval, times}, cbk => {
143
- return readFile(spawnChainDaemon.rpc_cert, (err, data) => {
144
- if (!!err) {
145
- return cbk([503, 'FailedToGetChainDaemonRpcCert', {err}]);
146
- }
147
-
148
- return cbk(null, data);
149
- });
150
- },
151
- cbk);
152
- }],
153
-
154
- // Generate a block to prevent lnd from getting stuck
155
- generateBlock: [
156
- 'getChainDaemonCert',
157
- 'miningKey',
158
- 'spawnChainDaemon',
159
- ({getChainDaemonCert, miningKey, spawnChainDaemon}, cbk) =>
160
- {
161
- return asyncRetry({interval, times}, cbk => {
162
- try {
163
- return generateBlocks({
164
- cert: getChainDaemonCert,
165
- count: 1,
166
- host: localhost,
167
- key: miningKey.public_key,
168
- pass: chainPass,
169
- port: spawnChainDaemon.rpc_port,
170
- user: chainUser,
171
- },
172
- cbk);
173
- } catch (err) {
174
- return cbk([503, 'FailedToGenerateBlockWhenSpawningLnd', {err}]);
175
- }
176
- },
177
- cbk);
178
- }],
179
-
180
- // Spawn LND
181
- spawnLightningDaemon: [
182
- 'generateBlock',
183
- 'getPorts',
184
- 'spawnChainDaemon',
185
- ({generateBlock, getPorts, spawnChainDaemon}, cbk) =>
186
- {
187
- return asyncRetry({interval, times}, cbk => {
188
- const {dir} = spawnChainDaemon;
189
-
190
- const arguments = [
191
- '--adminmacaroonpath', join(dir, adminMacaroonFileName),
192
- '--autopilot.heuristic', 'externalscore:0.5',
193
- '--autopilot.heuristic', 'preferential:0.5',
194
- '--bitcoin.active',
195
- '--bitcoin.chaindir', dir,
196
- '--bitcoin.minhtlc', '1000',
197
- '--bitcoin.node', 'btcd',
198
- '--bitcoin.regtest',
199
- '--datadir', dir,
200
- '--debuglevel', 'trace',
201
- '--externalip', `${localhost}:${getPorts.listen}`,
202
- '--historicalsyncinterval', '1s',
203
- '--invoicemacaroonpath', join(dir, invoiceMacaroonFileName),
204
- '--listen', `${localhost}:${getPorts.listen}`,
205
- '--logdir', join(dir, lightningDaemonLogPath),
206
- '--maxlogfilesize', 1,
207
- '--nobootstrap',
208
- '--readonlymacaroonpath', join(dir, readMacaroonFileName),
209
- '--restlisten', `${localhost}:${getPorts.rest}`,
210
- '--rpclisten', `${localhost}:${getPorts.rpc}`,
211
- '--tlscertpath', join(dir, lightningTlsCertFileName),
212
- '--tlskeypath', join(dir, lightningTlsKeyFileName),
213
- '--trickledelay', 1,
214
- '--unsafe-disconnect',
215
- ];
216
-
217
- const btcdArgs = [
218
- '--btcd.dir', dir,
219
- '--btcd.rpccert', join(dir, chainRpcCertName),
220
- '--btcd.rpchost', `${localhost}:${spawnChainDaemon.rpc_port}`,
221
- '--btcd.rpcpass', chainPass,
222
- '--btcd.rpcuser', chainUser,
223
- ];
224
-
225
- btcdArgs.forEach(n => arguments.push(n));
226
-
227
- const towerArgs = [
228
- '--watchtower.active',
229
- '--watchtower.externalip', `${localhost}:${getPorts.tower}`,
230
- '--watchtower.listen', `${localhost}:${getPorts.tower}`,
231
- '--watchtower.towerdir', dir,
232
- ]
233
-
234
- if (!!args.circular) {
235
- arguments.push('--allow-circular-route');
236
- }
237
-
238
- if (!!args.intercept) {
239
- arguments.push('--rpcmiddleware.enable')
240
- }
241
-
242
- if (!!args.keysend) {
243
- arguments.push('--accept-keysend');
244
- }
245
-
246
- if (!!args.noauth) {
247
- arguments.push('--no-macaroons');
248
- }
249
-
250
- if (!!args.tower) {
251
- towerArgs.forEach(n => arguments.push(n));
252
- }
253
-
254
- if (!!args.watchers) {
255
- arguments.push('--wtclient.active');
256
- }
257
-
258
- const daemon = spawn(lightningDaemonExecFileName, arguments);
259
-
260
- let isFinished = false;
261
- let isReady = false;
262
-
263
- const finished = (err, res) => {
264
- if (!!isFinished) {
265
- return;
266
- }
267
-
268
- isFinished = true;
269
-
270
- return cbk(err, res);
271
- };
272
-
273
- daemon.stderr.on('data', data => {
274
- daemon.kill();
275
- spawnChainDaemon.daemon.kill();
276
-
277
- if (/unknown.flag/.test(data.toString())) {
278
- return finished();
279
- }
280
-
281
- return finished([
282
- 503,
283
- 'FailedToStart',
284
- `${data}`.trim().split('\n'),
285
- ]);
286
- });
287
-
288
- daemon.stdout.on('data', data => {
289
- if (!isReady && /gRPC.proxy.started/.test(data+'')) {
290
- isReady = true;
291
-
292
- return finished(null, {daemon});
293
- };
294
-
295
- return;
296
- });
297
- },
298
- cbk);
299
- }],
300
-
301
- // Get the LND cert
302
- cert: [
303
- 'spawnChainDaemon',
304
- 'spawnLightningDaemon',
305
- ({spawnChainDaemon, spawnLightningDaemon}, cbk) =>
306
- {
307
- if (!spawnLightningDaemon) {
308
- return cbk([500, 'ExpectedLightningDaemon']);
309
- }
310
-
311
- const certPath = join(spawnChainDaemon.dir, lightningTlsCertFileName);
312
-
313
- return asyncRetry({interval, times}, cbk => {
314
- try {
315
- return cbk(null, readFileSync(certPath).toString('base64'));
316
- } catch (err) {
317
- return cbk([503, 'FailedToGetTlsCertWhenSpawningLnd', err]);
318
- }
319
- },
320
- cbk);
321
- }],
322
-
323
- // Get connection to the no-wallet lnd
324
- nonAuthenticatedLnd: [
325
- 'cert',
326
- 'getPorts',
327
- 'spawnLightningDaemon',
328
- ({cert, getPorts}, cbk) =>
329
- {
330
- const socket = `localhost:${getPorts.rpc}`;
331
-
332
- try {
333
- return cbk(null, unauthenticatedLndGrpc({cert, socket}).lnd);
334
- } catch (err) {
335
- return cbk([503, 'FailedToInstantiateNonAuthenticatedLnd', {err}]);
336
- }
337
- }],
338
-
339
- // Wait until the wallet is active
340
- waitForActive: ['nonAuthenticatedLnd', ({nonAuthenticatedLnd}, cbk) => {
341
- const events = [];
342
- const sub = subscribeToWalletStatus({lnd: nonAuthenticatedLnd});
343
-
344
- sub.once('absent', () => events.push('absent'));
345
- sub.once('starting', () => events.push('starting'));
346
-
347
- sub.once('active', () => {
348
- if (!events.includes('absent')) {
349
- return cbk([503, 'ExpectedWalletAbsentEvent']);
350
- }
351
-
352
- if (!events.includes('starting')) {
353
- return cbk([503, 'ExpectedWalletStartingEvent']);
354
- }
355
-
356
- events.push('active');
357
-
358
- sub.removeAllListeners();
359
-
360
- return cbk();
361
- });
362
-
363
- sub.once('error', err => {
364
- if (events.length === 3) {
365
- return;
366
- }
367
-
368
- // LND 0.12.1 and below do not support active tracking
369
- if (/unknown/.test(err.details)) {
370
- return cbk();
371
- }
372
-
373
- return cbk(err);
374
- });
375
-
376
- return;
377
- }],
378
-
379
- // Create seed
380
- createSeed: ['nonAuthenticatedLnd', ({nonAuthenticatedLnd}, cbk) => {
381
- // Exit early when a seed is pre-supplied
382
- if (!!args.seed) {
383
- return cbk(null, {seed: args.seed});
384
- }
385
-
386
- return asyncRetry({interval, times}, cbk => {
387
- return createSeed({
388
- lnd: nonAuthenticatedLnd,
389
- passphrase: lightningSeedPassphrase,
390
- },
391
- cbk);
392
- },
393
- cbk);
394
- }],
395
-
396
- // Create wallet
397
- createWallet: [
398
- 'createSeed',
399
- 'nonAuthenticatedLnd',
400
- ({createSeed, nonAuthenticatedLnd}, cbk) =>
401
- {
402
- return asyncRetry({interval, times}, cbk => {
403
- return createWallet({
404
- lnd: nonAuthenticatedLnd,
405
- passphrase: lightningSeedPassphrase,
406
- password: lightningWalletPassword,
407
- seed: createSeed.seed,
408
- },
409
- cbk);
410
- },
411
- cbk);
412
- }],
413
-
414
- // Get admin macaroon
415
- macaroon: [
416
- 'createWallet',
417
- 'spawnChainDaemon',
418
- ({spawnChainDaemon}, cbk) =>
419
- {
420
- // Exit early when spawning an LND that has no auth
421
- if (!!args.noauth) {
422
- return cbk();
423
- }
424
-
425
- const macaroonPath = join(spawnChainDaemon.dir, adminMacaroonFileName);
426
-
427
- return asyncRetry({interval, times}, cbk => {
428
- try {
429
- const macaroon = readFileSync(macaroonPath).toString('base64');
430
-
431
- if (!macaroon) {
432
- throw new Error('ExpectedMacaroonDataAtMacaroonPath');
433
- }
434
-
435
- return cbk(null, macaroon);
436
- } catch (err) {
437
- return cbk([503, 'FailedToGetAdminMacaroon', {err}]);
438
- }
439
- },
440
- cbk);
441
- }],
442
-
443
- // Wallet details
444
- wallet: [
445
- 'cert',
446
- 'getPorts',
447
- 'macaroon',
448
- ({cert, getPorts, macaroon}, cbk) =>
449
- {
450
- return cbk(null, {
451
- cert,
452
- macaroon,
453
- socket: `localhost:${getPorts.rpc}`,
454
- });
455
- }],
456
-
457
- // Instantiate lnd
458
- lnd: ['wallet', ({wallet}, cbk) => {
459
- try {
460
- const {lnd} = authenticatedLndGrpc({
461
- cert: wallet.cert,
462
- macaroon: wallet.macaroon,
463
- socket: wallet.socket,
464
- });
465
-
466
- return cbk(null, lnd);
467
- } catch (err) {
468
- return cbk([503, 'FailedToInstantiateLndWhenSpawning', {err}]);
469
- }
470
- }],
471
-
472
- // Delay to make sure everything has come together
473
- delay: ['lnd', 'waitForActive', ({lnd}, cbk) => {
474
- return asyncRetry(
475
- {interval, times},
476
- cbk => {
477
- return getWalletInfo({lnd}, (err, res) => {
478
- if (!!err) {
479
- return cbk(err);
480
- }
481
-
482
- if (!res.is_synced_to_chain) {
483
- return cbk([503, 'ExpectedNodeSyncToChain']);
484
- }
485
-
486
- return cbk(null, res);
487
- });
488
- },
489
- cbk
490
- );
491
- }],
492
- },
493
- (err, res) => {
494
- if (!!err) {
495
- return cbk(err);
496
- }
497
-
498
- const kill = () => {
499
- res.spawnChainDaemon.daemon.kill();
500
- res.spawnLightningDaemon.daemon.kill();
501
-
502
- setTimeout(() => {
503
- res.spawnLightningDaemon.daemon.kill('SIGKILL');
504
- },
505
- 1000 * 3);
506
-
507
- return;
508
- };
509
-
510
- const generate = ({count}) => new Promise(async (resolve, reject) => {
511
- try {
512
- return await asyncRetry({interval, times}, async () => {
513
- return resolve(await generateBlocks({
514
- count,
515
- cert: res.getChainDaemonCert,
516
- host: localhost,
517
- key: res.miningKey.public_key,
518
- pass: chainPass,
519
- port: res.spawnChainDaemon.rpc_port,
520
- user: chainUser,
521
- }));
522
- });
523
- } catch (err) {
524
- return reject(err);
525
- }
526
- });
527
-
528
- process.setMaxListeners(20);
529
-
530
- process.on('uncaughtException', err => {
531
- kill();
532
-
533
- return setTimeout(() => process.exit(1), 5000);
534
- });
535
-
536
- return cbk(null, {
537
- generate,
538
- kill,
539
- chain_listen_port: res.spawnChainDaemon.listen_port,
540
- chain_rpc_cert: res.spawnChainDaemon.rpc_cert,
541
- chain_rpc_cert_file: res.getChainDaemonCert,
542
- chain_rpc_pass: chainPass,
543
- chain_rpc_port: res.spawnChainDaemon.rpc_port,
544
- chain_rpc_user: chainUser,
545
- listen_ip: '127.0.0.1',
546
- listen_port: res.getPorts.listen,
547
- lnd: res.lnd,
548
- lnd_cert: res.wallet.cert,
549
- lnd_macaroon: res.wallet.macaroon,
550
- lnd_socket: res.wallet.socket,
551
- mining_key: res.miningKey.private_key,
552
- public_key: res.delay.public_key,
553
- rpc_port: res.getPorts.rpc,
554
- seed: res.createSeed.seed,
555
- socket: `127.0.0.1:${res.getPorts.listen}`,
556
- });
557
- });
558
- };