ln-docker-daemons 1.2.0 → 2.0.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Versions
2
2
 
3
+ ## 2.0.0
4
+
5
+ - `spawnLightningCluster`: add `socket` to reveal node LN p2p sockets
6
+ - `spawnLightningDocker`: add `add_chain_peer` method to add a chain peer
7
+ - `spawnLightningDocker`: add `chain_socket` method for the chain p2p socket
8
+ - `spawnLightningDocker`: add `ln_socket` method for the chain p2p socket
9
+
10
+ ### Breaking Changes
11
+
12
+ - Node 12+ is required
13
+
3
14
  ## 1.2.0
4
15
 
5
16
  - `spawnLightningCluster` add `generate` to generate coins for a node
package/README.md CHANGED
@@ -31,6 +31,7 @@ Spawn a Bitcoin Core Docker image
31
31
 
32
32
  @returns via cbk or Promise
33
33
  {
34
+ host: <Host String>
34
35
  kill: ({}, [cbk]) => <Kill Promise>
35
36
  rpc_pass: <RPC Password String>
36
37
  rpc_user: <RPC Username String>
@@ -68,6 +69,7 @@ Spawn a cluster of nodes
68
69
  id: <Node Public Key Hex String>
69
70
  kill: <Kill Function> ({}, cbk) => {}
70
71
  lnd: <Authenticated LND API Object>
72
+ socket: <Node Socket String>
71
73
  }]
72
74
  }
73
75
 
@@ -107,8 +109,11 @@ Spawn an LND Docker
107
109
 
108
110
  @returns via cbk or Promise
109
111
  {
112
+ add_chain_peer: <Add Peer Function> ({socket}) => {}
110
113
  cert: <LND Base64 Serialized TLS Cert>
114
+ chain_socket: <Chain P2P Socket String>
111
115
  kill: ({}, [cbk]) => <Kill LND Daemon Promise>
116
+ ln_socket: <LN P2P Socket String>
112
117
  macaroon: <LND Base64 Serialized Macaroon String>
113
118
  public_key: <Identity Public Key Hex String>
114
119
  socket: <LND RPC Host:Port Network Address String>
@@ -30,6 +30,7 @@ const trim = string => string.replace(/=+$/g, '');
30
30
 
31
31
  @returns via cbk or Promise
32
32
  {
33
+ host: <Host String>
33
34
  kill: ({}, [cbk]) => <Kill Promise>
34
35
  rpc_pass: <RPC Password String>
35
36
  rpc_user: <RPC Username String>
@@ -75,6 +76,7 @@ module.exports = (args, cbk) => {
75
76
  return spawnDockerImage({
76
77
  arguments: [
77
78
  '--disablewallet',
79
+ '--listen=1',
78
80
  '--persistmempool=false',
79
81
  '--printtoconsole',
80
82
  '--regtest',
@@ -86,12 +88,6 @@ module.exports = (args, cbk) => {
86
88
  `--zmqpubrawblock=tcp://*:${args.zmq_block_port}`,
87
89
  `--zmqpubrawtx=tcp://*:${args.zmq_tx_port}`,
88
90
  ],
89
- expose: [
90
- '18443/tcp',
91
- '18444/tcp',
92
- [`${args.zmq_block_port}/tcp`],
93
- [`${args.zmq_tx_port}/tcp`],
94
- ],
95
91
  image: dockerBitcoindImage,
96
92
  ports: {
97
93
  '18443/tcp': args.rpc_port,
@@ -0,0 +1,54 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {returnResult} = require('asyncjs-util');
3
+
4
+ const rpc = require('./rpc');
5
+
6
+ const action = 'add';
7
+ const cmd = 'addnode';
8
+ const host = 'localhost';
9
+
10
+ /** Add a node as a peer
11
+
12
+ {
13
+ pass: <RPC Password String>
14
+ port: <RPC Port Number>
15
+ socket: <Socket to Connect to String>
16
+ user: <RPC Username String>
17
+ }
18
+
19
+ @returns via cbk or Promise
20
+ */
21
+ module.exports = ({pass, port, socket, user}, cbk) => {
22
+ return new Promise((resolve, reject) => {
23
+ return asyncAuto({
24
+ // Check arguments
25
+ validate: cbk => {
26
+ if (!pass) {
27
+ return cbk([400, 'ExpectedRpcPasswordToAddChainPeer']);
28
+ }
29
+
30
+ if (!port) {
31
+ return cbk([400, 'ExpectedRpcPortNumberToAddChainPeer']);
32
+ }
33
+
34
+ if (!socket) {
35
+ return cbk([400, 'ExpectedSocketToAddChainPeer']);
36
+ }
37
+
38
+ if (!user) {
39
+ return cbk([400, 'ExpectedRpcUsernameToAddChainPeer']);
40
+ }
41
+
42
+ return cbk();
43
+ },
44
+
45
+ // Execute request
46
+ request: ['validate', ({}, cbk) => {
47
+ const params = [socket, action];
48
+
49
+ return rpc({cmd, host, pass, params, port, user}, cbk);
50
+ }],
51
+ },
52
+ returnResult({reject, resolve}, cbk));
53
+ });
54
+ };
@@ -0,0 +1,57 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {returnResult} = require('asyncjs-util');
3
+
4
+ const rpc = require('./rpc');
5
+
6
+ const cmd = 'getblock';
7
+ const host = 'localhost';
8
+ const infoVerbosityFlag = 1;
9
+
10
+ /** Get block info from Bitcoin Core daemon
11
+
12
+ {
13
+ id: <Block Hash Hex String>
14
+ pass: <RPC Password String>
15
+ port: <RPC Port Number>
16
+ user: <RPC Username String>
17
+ }
18
+
19
+ @returns via cbk or Promise
20
+ {
21
+ tx: [<Transaction Id Hex String>]
22
+ }
23
+ */
24
+ module.exports = ({id, pass, port, user}, cbk) => {
25
+ return new Promise((resolve, reject) => {
26
+ return asyncAuto({
27
+ // Check arguments
28
+ validate: cbk => {
29
+ if (!id) {
30
+ return cbk([400, 'ExpectedBlockIdToGetBlockInfo']);
31
+ }
32
+
33
+ if (!pass) {
34
+ return cbk([400, 'ExpectedRpcPasswordToGetBlockInfo']);
35
+ }
36
+
37
+ if (!port) {
38
+ return cbk([400, 'ExpectedRpcPortNumberToGetBlockInfo']);
39
+ }
40
+
41
+ if (!user) {
42
+ return cbk([400, 'ExpectedRpcUsernameToGetBlockInfo']);
43
+ }
44
+
45
+ return cbk();
46
+ },
47
+
48
+ // Execute request
49
+ request: ['validate', ({}, cbk) => {
50
+ const params = [id, infoVerbosityFlag];
51
+
52
+ return rpc({cmd, host, pass, params, port, user}, cbk);
53
+ }],
54
+ },
55
+ returnResult({reject, resolve, of: 'request'}, cbk));
56
+ });
57
+ };
@@ -1,4 +1,11 @@
1
+ const addPeer = require('./add_peer');
1
2
  const generateToAddress = require('./generate_to_address');
3
+ const getBlockInfo = require('./get_block_info');
2
4
  const getBlockchainInfo = require('./get_blockchain_info');
3
5
 
4
- module.exports = {generateToAddress, getBlockchainInfo};
6
+ module.exports = {
7
+ addPeer,
8
+ generateToAddress,
9
+ getBlockInfo,
10
+ getBlockchainInfo,
11
+ };
package/bitcoinrpc/rpc.js CHANGED
@@ -88,10 +88,6 @@ module.exports = ({cert, cmd, host, params, pass, port, user}, cbk) => {
88
88
 
89
89
  const {result} = await response.json();
90
90
 
91
- if (!result) {
92
- throw [503, 'ExpectedResultOfRpcRequest'];
93
- }
94
-
95
91
  return result;
96
92
  } catch (err) {
97
93
  if (err.code === 'ECONNRESET') {
@@ -1,16 +1,28 @@
1
1
  const asyncAuto = require('async/auto');
2
+ const asyncEach = require('async/each');
2
3
  const asyncMap = require('async/map');
4
+ const asyncRetry = require('async/retry');
3
5
  const {authenticatedLndGrpc} = require('lightning');
6
+ const {createChainAddress} = require('lightning');
4
7
  const {findFreePorts} = require('find-free-ports');
8
+ const {getUtxos} = require('lightning');
5
9
  const {getIdentity} = require('lightning');
6
10
  const {returnResult} = require('asyncjs-util');
7
11
 
8
12
  const {spawnLightningDocker} = require('./../lnd');
9
13
 
14
+ const between = (min, max) => Math.floor(Math.random() * (max - min) + min);
10
15
  const chunk = (arr, n, size) => [...Array(size)].map(_ => arr.splice(0, n));
11
16
  const count = size => size || 1;
17
+ const endPort = 65000;
12
18
  const generateAddress = '2N8hwP1WmJrFF5QWABn38y63uYLhnJYJYTF';
19
+ const interval = 10;
20
+ const makeAddress = ({lnd}) => createChainAddress({lnd, format: 'p2wpkh'});
21
+ const maturity = 100;
22
+ const pairs = n => n.map((x, i) => n.slice(i + 1).map(y => [x, y])).flat();
13
23
  const portsPerLnd = 6;
24
+ const startPort = 1025;
25
+ const times = 3000;
14
26
 
15
27
  /** Spawn a cluster of nodes
16
28
 
@@ -21,65 +33,120 @@ const portsPerLnd = 6;
21
33
  @returns via cbk or Promise
22
34
  {
23
35
  nodes: [{
24
- generate: ({address}, [cbk]) => {}
36
+ generate: ({address, count}, [cbk]) => {}
25
37
  id: <Node Public Key Hex String>
26
38
  kill: <Kill Function> ({}, cbk) => {}
27
39
  lnd: <Authenticated LND API Object>
40
+ socket: <Node Socket String>
28
41
  }]
29
42
  }
30
43
  */
31
- module.exports = ({size}, cbk) => {
44
+ module.exports = (args, cbk) => {
32
45
  return new Promise((resolve, reject) => {
33
46
  return asyncAuto({
34
- // Find ports for the requested daemons
35
- findPorts: async () => {
36
- return await findFreePorts(portsPerLnd * count(size));
37
- },
38
-
39
47
  // Spawn nodes
40
- spawn: ['findPorts', async ({findPorts}) => {
41
- const nodes = chunk(findPorts, portsPerLnd, count(size));
42
-
43
- return await asyncMap(nodes, async (ports) => {
44
- const [
45
- chainP2pPort,
46
- chainRpcPort,
47
- chainZmqBlockPort,
48
- chainZmqTxPort,
49
- lightningP2pPort,
50
- lightningRpcPort,
51
- ] = ports;
52
-
53
- const lightningDocker = await spawnLightningDocker({
54
- chain_p2p_port: chainP2pPort,
55
- chain_rpc_port: chainRpcPort,
56
- chain_zmq_block_port: chainZmqBlockPort,
57
- chain_zmq_tx_port: chainZmqTxPort,
58
- generate_address: generateAddress,
59
- lightning_p2p_port: lightningP2pPort,
60
- lightning_rpc_port: lightningRpcPort,
61
- });
48
+ spawn: async () => {
49
+ return await asyncRetry({}, async () => {
50
+ const options = {startPort: between(startPort, endPort)};
51
+
52
+ // Find ports for the requested daemons
53
+ const findPorts = await findFreePorts(
54
+ portsPerLnd * count(args.size),
55
+ options
56
+ );
57
+
58
+ const nodes = chunk(findPorts, portsPerLnd, count(args.size));
59
+
60
+ return await asyncMap(nodes, async (ports) => {
61
+ const [
62
+ chainP2pPort,
63
+ chainRpcPort,
64
+ chainZmqBlockPort,
65
+ chainZmqTxPort,
66
+ lightningP2pPort,
67
+ lightningRpcPort,
68
+ ] = ports;
69
+
70
+ const lightningDocker = await spawnLightningDocker({
71
+ chain_p2p_port: chainP2pPort,
72
+ chain_rpc_port: chainRpcPort,
73
+ chain_zmq_block_port: chainZmqBlockPort,
74
+ chain_zmq_tx_port: chainZmqTxPort,
75
+ generate_address: generateAddress,
76
+ lightning_p2p_port: lightningP2pPort,
77
+ lightning_rpc_port: lightningRpcPort,
78
+ });
79
+
80
+ const {lnd} = authenticatedLndGrpc({
81
+ cert: lightningDocker.cert,
82
+ macaroon: lightningDocker.macaroon,
83
+ socket: lightningDocker.socket,
84
+ });
62
85
 
63
- const {lnd} = authenticatedLndGrpc({
64
- cert: lightningDocker.cert,
65
- macaroon: lightningDocker.macaroon,
66
- socket: lightningDocker.socket,
86
+ const id = (await getIdentity({lnd})).public_key;
87
+
88
+ return {
89
+ id,
90
+ lnd,
91
+ chain: {
92
+ addPeer: lightningDocker.add_chain_peer,
93
+ generateToAddress: lightningDocker.generate,
94
+ getBlockInfo: lightningDocker.get_block_info,
95
+ socket: lightningDocker.chain_socket,
96
+ },
97
+ generate: ({address, count}) => {
98
+ return new Promise(async (resolve, reject) => {
99
+ await lightningDocker.generate({
100
+ count,
101
+ address: address || (await makeAddress({lnd})).address,
102
+ });
103
+
104
+ if (!count || count < maturity) {
105
+ return resolve();
106
+ }
107
+
108
+ await asyncRetry({interval, times}, async () => {
109
+ const [utxo] = (await getUtxos({lnd})).utxos;
110
+
111
+ if (!utxo) {
112
+ throw new Error('ExpectedUtxoInUtxos');
113
+ }
114
+
115
+ return utxo;
116
+ });
117
+
118
+ return resolve();
119
+ });
120
+ },
121
+ kill: lightningDocker.kill,
122
+ public_key: id,
123
+ socket: lightningDocker.ln_socket,
124
+ };
67
125
  });
126
+ });
127
+ },
68
128
 
69
- const id = (await getIdentity({lnd})).public_key;
129
+ // Connect nodes in the cluster to each other
130
+ connect: ['spawn', async ({spawn}) => {
131
+ return await asyncEach(pairs(spawn), async pair => {
132
+ const [a, b] = pair.map(({chain}) => chain);
70
133
 
71
- return {
72
- id,
73
- lnd,
74
- generate: lightningDocker.generate,
75
- kill: lightningDocker.kill,
76
- };
134
+ return await a.addPeer({socket: b.socket});
77
135
  });
78
136
  }],
79
137
 
80
138
  // Final set of nodes
81
- nodes: ['spawn', async ({spawn}) => {
82
- return {nodes: spawn};
139
+ nodes: ['connect', 'spawn', async ({spawn}) => {
140
+ return {
141
+ kill: ({}) => asyncEach(spawn, async ({kill}) => await kill({})),
142
+ nodes: spawn.map(node => ({
143
+ generate: node.generate,
144
+ id: node.id,
145
+ kill: node.kill,
146
+ lnd: node.lnd,
147
+ socket: node.socket,
148
+ })),
149
+ };
83
150
  }],
84
151
  },
85
152
  returnResult({reject, resolve, of: 'nodes'}, cbk));
@@ -1,4 +1,5 @@
1
1
  const asyncAuto = require('async/auto');
2
+ const asyncRetry = require('async/retry');
2
3
  const Dockerode = require('dockerode');
3
4
  const {returnResult} = require('asyncjs-util');
4
5
 
@@ -125,13 +126,16 @@ module.exports = ({arguments, expose, image, ports}, cbk) => {
125
126
 
126
127
  // Start the image
127
128
  start: ['container', 'docker', ({container, docker}, cbk) => {
128
- return container.start(err => {
129
- if (!!err) {
130
- return cbk([503, 'UnexpectedErrorStartingDockerContainer', {err}]);
131
- }
129
+ return asyncRetry({}, cbk => {
130
+ return container.start(err => {
131
+ if (!!err) {
132
+ return cbk([503, 'UnexpectedErrorStartingContainer', {err}]);
133
+ }
132
134
 
133
- return cbk();
134
- });
135
+ return cbk();
136
+ });
137
+ },
138
+ cbk);
135
139
  }],
136
140
 
137
141
  // Get the details about the container
@@ -1,3 +1,3 @@
1
1
  {
2
- "dockerLndImage": "lightninglabs/lnd:v0.13.1-beta"
2
+ "dockerLndImage": "lightninglabs/lnd:v0.13.4-beta"
3
3
  }
@@ -1,7 +1,9 @@
1
1
  const asyncAuto = require('async/auto');
2
2
  const {returnResult} = require('asyncjs-util');
3
3
 
4
+ const {addPeer} = require('./../bitcoinrpc');
4
5
  const {generateToAddress} = require('./../bitcoinrpc');
6
+ const {getBlockInfo} = require('./../bitcoinrpc');
5
7
  const {killDockers} = require('./../docker');
6
8
  const {spawnBitcoindDocker} = require('./../bitcoind');
7
9
  const spawnLndDocker = require('./spawn_lnd_docker');
@@ -20,9 +22,13 @@ const spawnLndDocker = require('./spawn_lnd_docker');
20
22
 
21
23
  @returns via cbk or Promise
22
24
  {
25
+ add_chain_peer: <Add Peer Function> ({socket}) => {}
23
26
  cert: <LND Base64 Serialized TLS Cert>
27
+ chain_socket: <Chain P2P Socket String>
24
28
  generate: ({address, count}, cbk) => <Generate to Address Promise>
29
+ get_block_info: <Get Block Info Function> ({id}) => {}
25
30
  kill: ({}, [cbk]) => <Kill LND Daemon Promise>
31
+ ln_socket: <LN P2P Socket String>
26
32
  macaroon: <LND Base64 Serialized Macaroon String>
27
33
  public_key: <Identity Public Key Hex String>
28
34
  socket: <LND RPC Host:Port Network Address String>
@@ -114,7 +120,14 @@ module.exports = (args, cbk) => {
114
120
  const dockers = [spawnChainDaemon, spawnLightningDaemon];
115
121
 
116
122
  return cbk(null, {
123
+ add_chain_peer: ({socket}) => addPeer({
124
+ socket,
125
+ pass: spawnChainDaemon.rpc_pass,
126
+ port: args.chain_rpc_port,
127
+ user: spawnChainDaemon.rpc_user,
128
+ }),
117
129
  cert: spawnLightningDaemon.cert,
130
+ chain_socket: `${spawnChainDaemon.host}`,
118
131
  generate: ({address, count}) => generateToAddress({
119
132
  address,
120
133
  count,
@@ -122,8 +135,15 @@ module.exports = (args, cbk) => {
122
135
  port: args.chain_rpc_port,
123
136
  user: spawnChainDaemon.rpc_user,
124
137
  }),
138
+ get_block_info: ({id}) => getBlockInfo({
139
+ id,
140
+ pass: spawnChainDaemon.rpc_pass,
141
+ port: args.chain_rpc_port,
142
+ user: spawnChainDaemon.rpc_user,
143
+ }),
125
144
  kill: ({}, cbk) => killDockers({dockers}, cbk),
126
145
  macaroon: spawnLightningDaemon.macaroon,
146
+ ln_socket: `${spawnLightningDaemon.host}:9735`,
127
147
  public_key: spawnLightningDaemon.public_key,
128
148
  socket: spawnLightningDaemon.socket,
129
149
  });
@@ -79,6 +79,8 @@ module.exports = (args, cbk) => {
79
79
 
80
80
  return spawnDockerImage({
81
81
  arguments: [
82
+ '--accept-keysend',
83
+ '--allow-circular-route',
82
84
  '--autopilot.heuristic=externalscore:0.5',
83
85
  '--autopilot.heuristic=preferential:0.5',
84
86
  '--bitcoin.active',
@@ -123,23 +125,26 @@ module.exports = (args, cbk) => {
123
125
 
124
126
  const {lnd} = unauthenticatedLndGrpc({cert, socket});
125
127
 
126
- lnd.unlocker.genSeed({}, (err, res) => {
127
- if (!!err) {
128
- return cbk([503, 'UnexpectedErrorGeneratingSeed', {err}]);
129
- }
130
-
131
- lnd.unlocker.initWallet({
132
- cipher_seed_mnemonic: res.cipher_seed_mnemonic,
133
- wallet_password: Buffer.from('password', 'utf8'),
134
- },
135
- err => {
128
+ return asyncRetry({interval, times}, cbk => {
129
+ return lnd.unlocker.genSeed({}, (err, res) => {
136
130
  if (!!err) {
137
- return cbk([503, 'UnexpectedErrorInitializingWallet', {err}]);
131
+ return cbk([503, 'UnexpectedErrorGeneratingSeed', {err}]);
138
132
  }
139
133
 
140
- return cbk();
134
+ lnd.unlocker.initWallet({
135
+ cipher_seed_mnemonic: res.cipher_seed_mnemonic,
136
+ wallet_password: Buffer.from('password', 'utf8'),
137
+ },
138
+ err => {
139
+ if (!!err) {
140
+ return cbk([503, 'UnexpectedErrorInitializingWallet', {err}]);
141
+ }
142
+
143
+ return cbk();
144
+ });
141
145
  });
142
- });
146
+ },
147
+ cbk);
143
148
  }],
144
149
 
145
150
  // Get the macaroon out of the docker image
@@ -183,6 +188,7 @@ module.exports = (args, cbk) => {
183
188
  {
184
189
  return cbk(null, {
185
190
  cert: getCertificate.file.toString('base64'),
191
+ host: spawnDocker.host,
186
192
  kill: spawnDocker.kill,
187
193
  macaroon: getMacaroon.file.toString('base64'),
188
194
  public_key: waitForRpc.public_key,
package/package.json CHANGED
@@ -8,11 +8,11 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@alexbosworth/node-fetch": "2.6.2",
11
- "async": "3.2.1",
12
- "asyncjs-util": "1.2.6",
11
+ "async": "3.2.2",
12
+ "asyncjs-util": "1.2.7",
13
13
  "dockerode": "3.3.1",
14
14
  "find-free-ports": "3.0.0",
15
- "lightning": "4.10.7",
15
+ "lightning": "4.14.4",
16
16
  "tar-stream": "2.2.0"
17
17
  },
18
18
  "description": "Spawn and control LN Docker daemons",
@@ -20,7 +20,7 @@
20
20
  "@alexbosworth/tap": "15.0.10"
21
21
  },
22
22
  "engines": {
23
- "node": ">=10.12.0"
23
+ "node": ">=12"
24
24
  },
25
25
  "keywords": [
26
26
  "docker"
@@ -35,5 +35,5 @@
35
35
  "scripts": {
36
36
  "test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -j 1 test/bitcoind/*.js test/lnd/*.js"
37
37
  },
38
- "version": "1.2.0"
38
+ "version": "2.0.0"
39
39
  }