balanceofsatoshis 11.49.2 → 11.52.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 +16 -0
- package/bos +8 -7
- package/commands/api.json +3 -0
- package/package.json +2 -2
- package/peers/channels_from_arguments.js +32 -15
- package/peers/open_channels.js +344 -150
- package/telegram/start_telegram_bot.js +18 -6
- package/test/integration/test_open_channels.js +113 -0
- package/test/peers/test_channels_from_arguments.js +62 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 11.52.0
|
|
4
|
+
|
|
5
|
+
- `call`: Add command `getMasterPublicKeys` to list extended public keys
|
|
6
|
+
|
|
7
|
+
## 11.51.0
|
|
8
|
+
|
|
9
|
+
- `telegram`: Support notifications when a channel is pending closing
|
|
10
|
+
|
|
11
|
+
## 11.50.1
|
|
12
|
+
|
|
13
|
+
- `telegram`: Support forwards and payments with `--use-proxy`
|
|
14
|
+
|
|
15
|
+
## 11.50.0
|
|
16
|
+
|
|
17
|
+
- `open`: Add `--opening-node` to batch open channels with multiple saved nodes
|
|
18
|
+
|
|
3
19
|
## 11.49.2
|
|
4
20
|
|
|
5
21
|
- `open`: Fix crash when using `--set-fee-rate` but policy details are missing
|
package/bos
CHANGED
|
@@ -44,6 +44,7 @@ const wallets = importLazy('./wallets');
|
|
|
44
44
|
const {version} = importLazy('./package');
|
|
45
45
|
|
|
46
46
|
const {BOOL} = prog;
|
|
47
|
+
const collect = arr => [].concat(...[arr]).filter(n => !!n);
|
|
47
48
|
const {exit} = process;
|
|
48
49
|
const flatten = arr => [].concat(...arr);
|
|
49
50
|
const {FLOAT} = prog;
|
|
@@ -1050,27 +1051,27 @@ prog
|
|
|
1050
1051
|
.option('--coop-close-address <addr>', 'Coop-close address', REPEATABLE)
|
|
1051
1052
|
.option('--external-funding', 'Use external funds for the channel open')
|
|
1052
1053
|
.option('--give <give_amount>', 'Amount to gift to peer', REPEATABLE)
|
|
1053
|
-
.option('--node <node_name>', '
|
|
1054
|
+
.option('--node <node_name>', 'Saved node to open channels')
|
|
1055
|
+
.option('--opening-node <node_name>', 'Open with saved node', REPEATABLE)
|
|
1054
1056
|
.option('--set-fee-rate <ppm>', 'Set forward fee rate to peer', REPEATABLE)
|
|
1055
1057
|
.option('--type <type>', 'Type of channel (private/public)', REPEATABLE)
|
|
1056
1058
|
.action((args, options, logger) => {
|
|
1057
1059
|
return new Promise(async (resolve, reject) => {
|
|
1058
|
-
const collect = n => flatten([n]).filter(n => !!n);
|
|
1059
|
-
|
|
1060
1060
|
try {
|
|
1061
1061
|
return peers.openChannels({
|
|
1062
1062
|
logger,
|
|
1063
1063
|
ask: (n, cbk) => inquirer.prompt([n]).then(res => cbk(res)),
|
|
1064
|
-
capacities:
|
|
1064
|
+
capacities: collect(options.amount),
|
|
1065
1065
|
cooperative_close_addresses: collect(options.coopCloseAddress),
|
|
1066
1066
|
fs: {getFile: readFile},
|
|
1067
|
-
gives:
|
|
1067
|
+
gives: collect(options.give),
|
|
1068
1068
|
is_external: options.externalFunding,
|
|
1069
1069
|
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
|
|
1070
|
+
opening_nodes: collect(options.openingNode),
|
|
1070
1071
|
public_keys: args.peerPublicKeys,
|
|
1071
1072
|
request: commands.simpleRequest,
|
|
1072
|
-
set_fee_rates:
|
|
1073
|
-
types:
|
|
1073
|
+
set_fee_rates: collect(options.setFeeRate),
|
|
1074
|
+
types: collect(options.type),
|
|
1074
1075
|
},
|
|
1075
1076
|
responses.returnObject({logger, reject, resolve}));
|
|
1076
1077
|
} catch (err) {
|
package/commands/api.json
CHANGED
package/package.json
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"ln-accounting": "5.0.5",
|
|
38
38
|
"ln-service": "53.8.0",
|
|
39
39
|
"ln-sync": "3.10.0",
|
|
40
|
-
"ln-telegram": "3.
|
|
40
|
+
"ln-telegram": "3.16.0",
|
|
41
41
|
"moment": "2.29.1",
|
|
42
42
|
"paid-services": "3.11.0",
|
|
43
43
|
"probing": "2.0.3",
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
|
|
82
82
|
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/arrays/*.js test/balances/*.js test/chain/*.js test/display/*.js test/encryption/*.js test/lnd/*.js test/network/*.js test/nodes/*.js test/peers/*.js test/responses/*.js test/routing/*.js test/services/*.js test/swaps/*.js test/tags/*.js test/wallets/*.js"
|
|
83
83
|
},
|
|
84
|
-
"version": "11.
|
|
84
|
+
"version": "11.52.0"
|
|
85
85
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const defaultChannelCapacity = 5e6;
|
|
2
|
+
const uniq = arr => Array.from(new Set(arr));
|
|
2
3
|
|
|
3
4
|
/** Derive channel to open details from channel argument list
|
|
4
5
|
|
|
@@ -6,32 +7,48 @@ const defaultChannelCapacity = 5e6;
|
|
|
6
7
|
addresses: [<Address String>]
|
|
7
8
|
capacities: [<Channel Capacity Tokens Number>]
|
|
8
9
|
gives: [<Give Tokens String>]
|
|
9
|
-
nodes: [<Node Identity Public Key Hex String>]
|
|
10
|
+
nodes: [<Channel Partner Node Identity Public Key Hex String>]
|
|
11
|
+
rates: [<Set Fee Rate String>]
|
|
12
|
+
saved: [<Open on Saved Node Name String>]
|
|
10
13
|
types: [<Channel Type String>]
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
@returns
|
|
14
17
|
{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
opens: [{
|
|
19
|
+
channels: [{
|
|
20
|
+
capacity: <Channel Capacity Tokens Number>
|
|
21
|
+
[cooperative_close_address]: <Restrict Coop Close to Address String>
|
|
22
|
+
[give_tokens]: <Give Tokens Number>
|
|
23
|
+
is_private: <Channel Is Private Bool>
|
|
24
|
+
partner_public_key: <Channel Partner Identity Public Key Hex String>
|
|
25
|
+
[rate]: <Set Fee Rate String>
|
|
26
|
+
}]
|
|
27
|
+
[node]: <Saved Node Name String>
|
|
20
28
|
}]
|
|
21
29
|
}
|
|
22
30
|
*/
|
|
23
|
-
module.exports =
|
|
24
|
-
const channels = nodes.map((key, i) => {
|
|
25
|
-
const type = types[i] || undefined;
|
|
26
|
-
|
|
31
|
+
module.exports = args => {
|
|
32
|
+
const channels = args.nodes.map((key, i) => {
|
|
27
33
|
return {
|
|
28
|
-
capacity: capacities[i] || defaultChannelCapacity,
|
|
29
|
-
cooperative_close_address:
|
|
30
|
-
give_tokens: !!gives[i] ? Number(gives[i]) : undefined,
|
|
31
|
-
is_private: !!
|
|
34
|
+
capacity: args.capacities[i] || defaultChannelCapacity,
|
|
35
|
+
cooperative_close_address: args.addresses[i] || undefined,
|
|
36
|
+
give_tokens: !!args.gives[i] ? Number(args.gives[i]) : undefined,
|
|
37
|
+
is_private: !!args.types[i] && args.types[i] === 'private',
|
|
38
|
+
node: args.saved[i] || undefined,
|
|
32
39
|
partner_public_key: key,
|
|
40
|
+
rate: args.rates[i] || undefined,
|
|
33
41
|
};
|
|
34
42
|
});
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
// Exit early when there are no saved nodes to use
|
|
45
|
+
if (!args.saved.length) {
|
|
46
|
+
return {opens: [{channels}]};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const opens = uniq(args.saved).map(node => {
|
|
50
|
+
return {node, channels: channels.filter(n => n.node === node)};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return {opens};
|
|
37
54
|
};
|
package/peers/open_channels.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const {randomBytes} = require('crypto');
|
|
2
2
|
|
|
3
|
+
const {acceptsChannelOpen} = require('ln-sync');
|
|
3
4
|
const {addPeer} = require('ln-service');
|
|
4
5
|
const {address} = require('bitcoinjs-lib');
|
|
5
6
|
const {askForFeeRate} = require('ln-sync');
|
|
@@ -8,8 +9,10 @@ const asyncEach = require('async/each');
|
|
|
8
9
|
const asyncEachSeries = require('async/eachSeries');
|
|
9
10
|
const asyncDetectSeries = require('async/detectSeries');
|
|
10
11
|
const asyncMap = require('async/map');
|
|
12
|
+
const asyncMapSeries = require('async/mapSeries');
|
|
11
13
|
const asyncReflect = require('async/reflect');
|
|
12
14
|
const asyncRetry = require('async/retry');
|
|
15
|
+
const {broadcastChainTransaction} = require('ln-service');
|
|
13
16
|
const {cancelPendingChannel} = require('ln-service');
|
|
14
17
|
const {fundPendingChannels} = require('ln-service');
|
|
15
18
|
const {getFundedTransaction} = require('ln-sync');
|
|
@@ -26,16 +29,19 @@ const {Transaction} = require('bitcoinjs-lib');
|
|
|
26
29
|
const {unlockUtxo} = require('ln-service');
|
|
27
30
|
|
|
28
31
|
const adjustFees = require('./../routing/adjust_fees');
|
|
32
|
+
const {authenticatedLnd} = require('./../lnd');
|
|
29
33
|
const channelsFromArguments = require('./channels_from_arguments');
|
|
30
34
|
const {getAddressUtxo} = require('./../chain');
|
|
31
35
|
const {parseAmount} = require('./../display');
|
|
32
36
|
|
|
33
37
|
const bech32AsData = bech32 => address.fromBech32(bech32).data;
|
|
38
|
+
const detectNetworks = ['btc', 'btctestnet'];
|
|
39
|
+
const flatten = arr => [].concat(...arr);
|
|
34
40
|
const format = 'p2wpkh';
|
|
35
41
|
const {isArray} = Array;
|
|
36
42
|
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
37
|
-
const lineBreak = '\n';
|
|
38
43
|
const knownTypes = ['private', 'public'];
|
|
44
|
+
const lineBreak = '\n';
|
|
39
45
|
const noInternalFundingVersions = ['0.11.0-beta', '0.11.1-beta'];
|
|
40
46
|
const notFound = -1;
|
|
41
47
|
const peerAddedDelayMs = 1000 * 5;
|
|
@@ -60,6 +66,7 @@ const utxoPollingTimes = 20;
|
|
|
60
66
|
[is_external]: <Use External Funds to Open Channels Bool>
|
|
61
67
|
lnd: <Authenticated LND API Object>
|
|
62
68
|
logger: <Winston Logger Object>
|
|
69
|
+
opening_nodes: [<Open New Channel With Saved Node Name String>]
|
|
63
70
|
public_keys: [<Public Key Hex String>]
|
|
64
71
|
request: <Request Function>
|
|
65
72
|
set_fee_rates: [<Fee Rate Number>]
|
|
@@ -100,6 +107,10 @@ module.exports = (args, cbk) => {
|
|
|
100
107
|
return cbk([400, 'ExpectedLoggerToInitiateOpenChannelRequests']);
|
|
101
108
|
}
|
|
102
109
|
|
|
110
|
+
if (!isArray(args.opening_nodes)) {
|
|
111
|
+
return cbk([400, 'ExpectedOpeningNodesArrayToInitiateOpenChannels']);
|
|
112
|
+
}
|
|
113
|
+
|
|
103
114
|
if (!isArray(args.public_keys)) {
|
|
104
115
|
return cbk([400, 'ExpectedPublicKeysToOpenChannels']);
|
|
105
116
|
}
|
|
@@ -112,6 +123,7 @@ module.exports = (args, cbk) => {
|
|
|
112
123
|
const hasCapacities = !!args.capacities.length;
|
|
113
124
|
const hasGives = !!args.gives.length;
|
|
114
125
|
const hasFeeRates = !!args.set_fee_rates.length;
|
|
126
|
+
const hasNodes = !!args.opening_nodes.length;
|
|
115
127
|
const publicKeysLength = args.public_keys.length;
|
|
116
128
|
|
|
117
129
|
if (!!hasCapacities && publicKeysLength !== args.capacities.length) {
|
|
@@ -130,6 +142,10 @@ module.exports = (args, cbk) => {
|
|
|
130
142
|
return cbk([400, 'MustSetFeeRateForEveryPublicKey']);
|
|
131
143
|
}
|
|
132
144
|
|
|
145
|
+
if (!!hasNodes && publicKeysLength !== args.opening_nodes.length) {
|
|
146
|
+
return cbk([400, 'MustSetOpeningNodeForEveryPublicKey']);
|
|
147
|
+
}
|
|
148
|
+
|
|
133
149
|
if (!args.request) {
|
|
134
150
|
return cbk([400, 'ExpectedRequestFunctionToOpenChannels']);
|
|
135
151
|
}
|
|
@@ -162,13 +178,33 @@ module.exports = (args, cbk) => {
|
|
|
162
178
|
return cbk(null, capacities);
|
|
163
179
|
}],
|
|
164
180
|
|
|
165
|
-
// Get
|
|
181
|
+
// Get LNDs associated with nodes specified for opening
|
|
182
|
+
getLnds: ['validate', ({}, cbk) => {
|
|
183
|
+
// Exit early when there are no opening nodes specified
|
|
184
|
+
if (!args.opening_nodes.length) {
|
|
185
|
+
return cbk(null, [{lnd: args.lnd}]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return asyncMapSeries(uniq(args.opening_nodes), (node, cbk) => {
|
|
189
|
+
return authenticatedLnd({node, logger: args.logger}, (err, res) => {
|
|
190
|
+
if (!!err) {
|
|
191
|
+
return cbk(err);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return cbk(null, {node, lnd: res.lnd});
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
cbk);
|
|
198
|
+
}],
|
|
199
|
+
|
|
200
|
+
// Get the default network name
|
|
166
201
|
getNetwork: ['validate', ({}, cbk) => getNetwork({lnd: args.lnd}, cbk)],
|
|
167
202
|
|
|
168
203
|
// Get sockets in case we need to connect
|
|
169
204
|
getNodes: ['validate', ({}, cbk) => {
|
|
170
205
|
return asyncMap(uniq(args.public_keys), (key, cbk) => {
|
|
171
206
|
return getNode({lnd: args.lnd, public_key: key}, (err, res) => {
|
|
207
|
+
// Ignore errors when a node is unknown in the graph
|
|
172
208
|
if (!!err) {
|
|
173
209
|
return cbk(null, {public_key: key, sockets: []});
|
|
174
210
|
}
|
|
@@ -192,35 +228,89 @@ module.exports = (args, cbk) => {
|
|
|
192
228
|
cbk);
|
|
193
229
|
}],
|
|
194
230
|
|
|
195
|
-
// Get
|
|
196
|
-
getPeers: ['validate', ({}, cbk) => getPeers({lnd: args.lnd}, cbk)],
|
|
197
|
-
|
|
198
|
-
// Get the wallet version and check if it is compatible
|
|
231
|
+
// Get the wallet version to make sure the node supports internal funding
|
|
199
232
|
getWalletVersion: ['validate', ({}, cbk) => {
|
|
200
|
-
return getWalletVersion({lnd: args.lnd},
|
|
201
|
-
|
|
202
|
-
return cbk([400, 'BackingLndCannotBeUsedToOpenChannels', {err}]);
|
|
203
|
-
}
|
|
233
|
+
return getWalletVersion({lnd: args.lnd}, cbk);
|
|
234
|
+
}],
|
|
204
235
|
|
|
205
|
-
|
|
206
|
-
|
|
236
|
+
// Get the networks of the opening nodes
|
|
237
|
+
getOpeningNetworks: ['getLnds', ({getLnds}, cbk) => {
|
|
238
|
+
if (!getLnds) {
|
|
239
|
+
return cbk();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return asyncMap(getLnds, ({lnd}, cbk) => getNetwork({lnd}, cbk), cbk);
|
|
207
243
|
}],
|
|
208
244
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
'getNodes',
|
|
213
|
-
'getPeers',
|
|
214
|
-
({capacities, getNodes, getPeers}, cbk) =>
|
|
215
|
-
{
|
|
216
|
-
const {channels} = channelsFromArguments({
|
|
245
|
+
// Get the opening parameters to use to open the new channels
|
|
246
|
+
opens: ['capacities', ({capacities}, cbk) => {
|
|
247
|
+
const {opens} = channelsFromArguments({
|
|
217
248
|
capacities,
|
|
218
249
|
addresses: args.cooperative_close_addresses,
|
|
219
250
|
gives: args.gives,
|
|
220
251
|
nodes: args.public_keys,
|
|
252
|
+
rates: args.set_fee_rates,
|
|
253
|
+
saved: args.opening_nodes,
|
|
221
254
|
types: args.types,
|
|
222
255
|
});
|
|
223
256
|
|
|
257
|
+
return cbk(null, opens);
|
|
258
|
+
}],
|
|
259
|
+
|
|
260
|
+
// Check if all networks are the same
|
|
261
|
+
checkNetworks: [
|
|
262
|
+
'getNetwork',
|
|
263
|
+
'getOpeningNetworks',
|
|
264
|
+
({getNetwork, getOpeningNetworks}, cbk) =>
|
|
265
|
+
{
|
|
266
|
+
// Exit early when there are no networks to check
|
|
267
|
+
if (!getOpeningNetworks) {
|
|
268
|
+
return cbk();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!!getOpeningNetworks.find(n => n.network !== getNetwork.network)) {
|
|
272
|
+
return cbk([400, 'AllOpeningNodesMustBeOnSameChain']);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return cbk();
|
|
276
|
+
}],
|
|
277
|
+
|
|
278
|
+
// Get connected peers to see if we are already connected
|
|
279
|
+
getPeers: ['getLnds', ({getLnds}, cbk) => {
|
|
280
|
+
// Exit early when there are no opening nodes
|
|
281
|
+
if (!args.opening_nodes.length) {
|
|
282
|
+
return getPeers({lnd: args.lnd}, (err, res) => {
|
|
283
|
+
if (!!err) {
|
|
284
|
+
return cbk(err);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return cbk(null, [{peers: res.peers}]);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return asyncMap(args.opening_nodes, (node, cbk) => {
|
|
292
|
+
const {lnd} = getLnds.find(n => n.node === node);
|
|
293
|
+
|
|
294
|
+
return getPeers({lnd}, (err, res) => {
|
|
295
|
+
if (!!err) {
|
|
296
|
+
return cbk(err);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return cbk(null, {node, peers: res.peers});
|
|
300
|
+
});
|
|
301
|
+
},
|
|
302
|
+
cbk);
|
|
303
|
+
}],
|
|
304
|
+
|
|
305
|
+
// Connect up to the peers
|
|
306
|
+
connect: [
|
|
307
|
+
'getLnds',
|
|
308
|
+
'getNodes',
|
|
309
|
+
'getPeers',
|
|
310
|
+
'opens',
|
|
311
|
+
({getLnds, getNodes, getPeers, opens}, cbk) =>
|
|
312
|
+
{
|
|
313
|
+
// Collect some details about nodes being connected to
|
|
224
314
|
const nodes = getNodes.filter(n => !!n.channels_count).map(node => {
|
|
225
315
|
return {
|
|
226
316
|
node: `${node.alias || node.public_key}`,
|
|
@@ -231,49 +321,67 @@ module.exports = (args, cbk) => {
|
|
|
231
321
|
|
|
232
322
|
args.logger.info(nodes);
|
|
233
323
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
324
|
+
// Connect up as peers
|
|
325
|
+
return asyncEach(opens, ({node, channels}, cbk) => {
|
|
326
|
+
// Summarize who is being opened to
|
|
327
|
+
const openingTo = getNodes
|
|
328
|
+
.filter(remote => {
|
|
329
|
+
return !!channels.find(channel => {
|
|
330
|
+
return channel.partner_public_key === remote.public_key;
|
|
331
|
+
});
|
|
332
|
+
})
|
|
333
|
+
.map(remote => {
|
|
334
|
+
const {capacity} = channels.find(channel => {
|
|
335
|
+
return channel.partner_public_key === remote.public_key;
|
|
336
|
+
});
|
|
238
337
|
|
|
239
|
-
|
|
240
|
-
});
|
|
338
|
+
const remoteNamed = remote.alias || remote.public_key;
|
|
241
339
|
|
|
242
|
-
|
|
340
|
+
return `${remoteNamed}: ${tokAsBigUnit(capacity)}`;
|
|
341
|
+
});
|
|
243
342
|
|
|
244
|
-
|
|
245
|
-
// Exit early when the peer is already connected
|
|
246
|
-
if (getPeers.peers.map(n => n.public_key).includes(key)) {
|
|
247
|
-
return cbk();
|
|
248
|
-
}
|
|
343
|
+
args.logger.info({node, opening_to: openingTo});
|
|
249
344
|
|
|
250
|
-
const
|
|
345
|
+
const connectToKeys = channels.map(n => n.partner_public_key);
|
|
346
|
+
const {lnd} = getLnds.find(n => n.node === node);
|
|
347
|
+
const {peers} = getPeers.find(n => n.node === node);
|
|
251
348
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
connecting_to: {alias: node.alias, public_key: node.public_key},
|
|
258
|
-
});
|
|
349
|
+
return asyncEach(connectToKeys, (key, cbk) => {
|
|
350
|
+
// Exit early when the peer is already connected
|
|
351
|
+
if (peers.map(n => n.public_key).includes(key)) {
|
|
352
|
+
return cbk();
|
|
353
|
+
}
|
|
259
354
|
|
|
260
|
-
|
|
261
|
-
return asyncDetectSeries(node.sockets, ({socket}, cbk) => {
|
|
262
|
-
return addPeer({socket, lnd: args.lnd, public_key: key}, err => {
|
|
263
|
-
return cbk(null, !err);
|
|
264
|
-
});
|
|
265
|
-
},
|
|
266
|
-
(err, res) => {
|
|
267
|
-
if (!!err) {
|
|
268
|
-
return cbk(err);
|
|
269
|
-
}
|
|
355
|
+
const to = getNodes.find(n => n.public_key === key);
|
|
270
356
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
357
|
+
if (!to.sockets.length) {
|
|
358
|
+
return cbk([503, 'NoAddressFoundToConnectToNode', {to}]);
|
|
359
|
+
}
|
|
274
360
|
|
|
275
|
-
|
|
361
|
+
args.logger.info({
|
|
362
|
+
connecting_to: {alias: to.alias, public_key: to.public_key},
|
|
363
|
+
from: node,
|
|
276
364
|
});
|
|
365
|
+
|
|
366
|
+
return asyncRetry({times}, cbk => {
|
|
367
|
+
return asyncDetectSeries(to.sockets, ({socket}, cbk) => {
|
|
368
|
+
return addPeer({lnd, socket, public_key: key}, err => {
|
|
369
|
+
return cbk(null, !err);
|
|
370
|
+
});
|
|
371
|
+
},
|
|
372
|
+
(err, res) => {
|
|
373
|
+
if (!!err) {
|
|
374
|
+
return cbk(err);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!res) {
|
|
378
|
+
return cbk([503, 'FailedToConnectToPeer', ({peer: key})]);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return setTimeout(() => cbk(null, true), peerAddedDelayMs);
|
|
382
|
+
});
|
|
383
|
+
},
|
|
384
|
+
cbk);
|
|
277
385
|
},
|
|
278
386
|
cbk);
|
|
279
387
|
},
|
|
@@ -282,40 +390,33 @@ module.exports = (args, cbk) => {
|
|
|
282
390
|
|
|
283
391
|
// Check all nodes that they will allow an inbound channel
|
|
284
392
|
checkAcceptance: [
|
|
285
|
-
'capacities',
|
|
286
393
|
'connect',
|
|
287
|
-
|
|
394
|
+
'getLnds',
|
|
395
|
+
'opens',
|
|
396
|
+
({connect, getLnds, opens}, cbk) =>
|
|
288
397
|
{
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
398
|
+
// Flatten out the opens so that they can be tried serially
|
|
399
|
+
const tests = opens.map(({channels, node}) => {
|
|
400
|
+
return channels.map(channel => ({
|
|
401
|
+
capacity: channel.capacity,
|
|
402
|
+
cooperative_close_address: channel.cooperative_close_address,
|
|
403
|
+
give_tokens: channel.give_tokens,
|
|
404
|
+
is_private: channel.is_private,
|
|
405
|
+
lnd: getLnds.find(n => n.node === node).lnd,
|
|
406
|
+
partner_public_key: channel.partner_public_key,
|
|
407
|
+
}));
|
|
295
408
|
});
|
|
296
409
|
|
|
297
|
-
return asyncEachSeries(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
410
|
+
return asyncEachSeries(flatten(tests), (test, cbk) => {
|
|
411
|
+
return acceptsChannelOpen({
|
|
412
|
+
capacity: test.capacity,
|
|
413
|
+
cooperative_close_address: test.cooperative_close_address,
|
|
414
|
+
give_tokens: test.give_tokens,
|
|
415
|
+
is_private: test.is_private,
|
|
416
|
+
lnd: test.lnd,
|
|
417
|
+
partner_public_key: test.partner_public_key,
|
|
303
418
|
},
|
|
304
|
-
|
|
305
|
-
if (!!err) {
|
|
306
|
-
return cbk([503, 'UnexpectedErrorProposingChannel', {to, err}]);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const [{id}] = res.pending;
|
|
310
|
-
|
|
311
|
-
return cancelPendingChannel({id, lnd: args.lnd}, (err, res) => {
|
|
312
|
-
if (!!err) {
|
|
313
|
-
return cbk([503, 'UnexpectedErrorCancelingChannel', {err}]);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return cbk(null, false);
|
|
317
|
-
});
|
|
318
|
-
});
|
|
419
|
+
cbk);
|
|
319
420
|
},
|
|
320
421
|
cbk);
|
|
321
422
|
}],
|
|
@@ -341,7 +442,7 @@ module.exports = (args, cbk) => {
|
|
|
341
442
|
// Peers are connected - what type of funding will be used?
|
|
342
443
|
args.logger.info(lineBreak);
|
|
343
444
|
|
|
344
|
-
// Prompt to make sure that internal funding should
|
|
445
|
+
// Prompt to make sure that internal funding should be used
|
|
345
446
|
return args.ask({
|
|
346
447
|
default: true,
|
|
347
448
|
message: 'Use internal wallet funds?',
|
|
@@ -366,39 +467,77 @@ module.exports = (args, cbk) => {
|
|
|
366
467
|
'askForFeeRate',
|
|
367
468
|
'capacities',
|
|
368
469
|
'connect',
|
|
369
|
-
'
|
|
470
|
+
'getLnds',
|
|
471
|
+
'getNodes',
|
|
370
472
|
'isExternal',
|
|
371
|
-
|
|
473
|
+
'opens',
|
|
474
|
+
({getLnds, getNodes, opens}, cbk) =>
|
|
372
475
|
{
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
addresses: args.cooperative_close_addresses,
|
|
376
|
-
gives: args.gives,
|
|
377
|
-
nodes: args.public_keys,
|
|
378
|
-
types: args.types,
|
|
379
|
-
});
|
|
476
|
+
// When there are multiple batches, broadcasting must be stopped
|
|
477
|
+
const [, hasMultipleBatches] = opens;
|
|
380
478
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
479
|
+
// Go through each batch and open channels
|
|
480
|
+
return asyncMapSeries(opens, asyncReflect(({channels, node}, cbk) => {
|
|
481
|
+
const {lnd} = getLnds.find(n => n.node === node);
|
|
482
|
+
|
|
483
|
+
return openChannels({
|
|
484
|
+
channels,
|
|
485
|
+
lnd,
|
|
486
|
+
is_avoiding_broadcast: !!hasMultipleBatches,
|
|
487
|
+
},
|
|
488
|
+
(err, res) => {
|
|
489
|
+
if (!!err) {
|
|
490
|
+
return cbk(err);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return cbk(null, {lnd, node, pending: res.pending});
|
|
494
|
+
});
|
|
495
|
+
}),
|
|
496
|
+
(err, res) => {
|
|
497
|
+
const openError = res.find(n => !!n.error);
|
|
498
|
+
const opening = res.map(n => n.value).filter(n => !!n);
|
|
499
|
+
|
|
500
|
+
if (!!openError) {
|
|
501
|
+
// Cancel past successful batch channel open proposals
|
|
502
|
+
return asyncEach(opening, ({lnd, pending}, cbk) => {
|
|
503
|
+
return asyncEach(pending, ({id}, cbk) => {
|
|
504
|
+
return cancelPendingChannel({id, lnd}, err => {
|
|
505
|
+
// Suppress errors
|
|
506
|
+
return cbk();
|
|
507
|
+
});
|
|
508
|
+
},
|
|
509
|
+
cbk);
|
|
510
|
+
},
|
|
511
|
+
() => {
|
|
512
|
+
// Return the original error
|
|
513
|
+
return cbk(openError.error);
|
|
514
|
+
});
|
|
384
515
|
}
|
|
385
516
|
|
|
386
|
-
|
|
517
|
+
return cbk(null, res.map(n => n.value));
|
|
518
|
+
});
|
|
519
|
+
}],
|
|
520
|
+
|
|
521
|
+
// Pending channel outputs
|
|
522
|
+
outputs: ['openChannels', ({openChannels}, cbk) => {
|
|
523
|
+
// All batches will be paid out together in a single tx
|
|
524
|
+
const pending = flatten(openChannels.map(({pending}) => {
|
|
525
|
+
return pending.map(n => ({address: n.address, tokens: n.tokens}));
|
|
526
|
+
}));
|
|
387
527
|
|
|
388
|
-
|
|
389
|
-
|
|
528
|
+
// Sort all the outputs using BIP 69
|
|
529
|
+
try {
|
|
390
530
|
pending.sort((a, b) => {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
531
|
+
// Sort by tokens ascending when no tie breaker needed
|
|
532
|
+
if (a.tokens !== b.tokens) {
|
|
533
|
+
return a.tokens - b.tokens;
|
|
534
|
+
}
|
|
395
535
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
536
|
+
return bech32AsData(a.address).compare(bech32AsData(b.address));
|
|
537
|
+
});
|
|
538
|
+
} catch (err) {}
|
|
399
539
|
|
|
400
|
-
|
|
401
|
-
});
|
|
540
|
+
return cbk(null, pending);
|
|
402
541
|
}],
|
|
403
542
|
|
|
404
543
|
// Detect funding transaction
|
|
@@ -407,13 +546,19 @@ module.exports = (args, cbk) => {
|
|
|
407
546
|
'openChannels',
|
|
408
547
|
({getNetwork, openChannels}, cbk) =>
|
|
409
548
|
{
|
|
549
|
+
if (!detectNetworks.includes(getNetwork.network)) {
|
|
550
|
+
return cbk();
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const [{pending}] = openChannels;
|
|
554
|
+
|
|
555
|
+
const [{address, tokens}] = pending;
|
|
556
|
+
|
|
410
557
|
return asyncRetry({
|
|
411
558
|
interval: utxoPollingIntervalMs,
|
|
412
559
|
times: utxoPollingTimes,
|
|
413
560
|
},
|
|
414
561
|
cbk => {
|
|
415
|
-
const [{address, tokens}] = openChannels.pending;
|
|
416
|
-
|
|
417
562
|
return getAddressUtxo({
|
|
418
563
|
address,
|
|
419
564
|
tokens,
|
|
@@ -445,12 +590,15 @@ module.exports = (args, cbk) => {
|
|
|
445
590
|
funding_detected: Transaction.fromHex(foundTx).getId(),
|
|
446
591
|
});
|
|
447
592
|
|
|
448
|
-
return
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
593
|
+
return asyncEach(openChannels, ({lnd, node, pending}, cbk) => {
|
|
594
|
+
return fundPendingChannels({
|
|
595
|
+
lnd,
|
|
596
|
+
channels: pending.map(n => n.id),
|
|
597
|
+
funding: res.psbt,
|
|
598
|
+
},
|
|
599
|
+
() => cbk());
|
|
452
600
|
},
|
|
453
|
-
|
|
601
|
+
cbk);
|
|
454
602
|
});
|
|
455
603
|
});
|
|
456
604
|
},
|
|
@@ -464,8 +612,8 @@ module.exports = (args, cbk) => {
|
|
|
464
612
|
getFunding: [
|
|
465
613
|
'askForFeeRate',
|
|
466
614
|
'isExternal',
|
|
467
|
-
'
|
|
468
|
-
asyncReflect(({askForFeeRate, isExternal,
|
|
615
|
+
'outputs',
|
|
616
|
+
asyncReflect(({askForFeeRate, isExternal, outputs}, cbk) =>
|
|
469
617
|
{
|
|
470
618
|
// Warn external funding that funds are expected within 10 minutes
|
|
471
619
|
if (!!isExternal) {
|
|
@@ -475,15 +623,12 @@ module.exports = (args, cbk) => {
|
|
|
475
623
|
}
|
|
476
624
|
|
|
477
625
|
return getFundedTransaction({
|
|
626
|
+
outputs,
|
|
478
627
|
ask: args.ask,
|
|
479
628
|
chain_fee_tokens_per_vbyte: askForFeeRate.tokens_per_vbyte,
|
|
480
629
|
is_external: isExternal,
|
|
481
630
|
lnd: args.lnd,
|
|
482
631
|
logger: args.logger,
|
|
483
|
-
outputs: openChannels.pending.map(({address, tokens}) => ({
|
|
484
|
-
address,
|
|
485
|
-
tokens,
|
|
486
|
-
})),
|
|
487
632
|
},
|
|
488
633
|
cbk);
|
|
489
634
|
})],
|
|
@@ -528,25 +673,65 @@ module.exports = (args, cbk) => {
|
|
|
528
673
|
fundChannels: [
|
|
529
674
|
'fundingPsbt',
|
|
530
675
|
'openChannels',
|
|
531
|
-
|
|
676
|
+
'outputs',
|
|
677
|
+
asyncReflect(({fundingPsbt, openChannels, outputs}, cbk) =>
|
|
532
678
|
{
|
|
533
679
|
// Exit early when there is no funding PSBT
|
|
534
680
|
if (!fundingPsbt.value || !fundingPsbt.value.psbt) {
|
|
535
681
|
return cbk(null, {});
|
|
536
682
|
}
|
|
537
683
|
|
|
538
|
-
args.logger.info({
|
|
539
|
-
funding: openChannels.pending.map(n => tokAsBigUnit(n.tokens)),
|
|
540
|
-
});
|
|
684
|
+
args.logger.info({funding: outputs.map(n => tokAsBigUnit(n.tokens))});
|
|
541
685
|
|
|
542
|
-
return
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
686
|
+
return asyncMap(openChannels, ({lnd, node, pending}, cbk) => {
|
|
687
|
+
return fundPendingChannels({
|
|
688
|
+
lnd,
|
|
689
|
+
channels: pending.map(n => n.id),
|
|
690
|
+
funding: fundingPsbt.value.psbt,
|
|
691
|
+
},
|
|
692
|
+
cbk);
|
|
546
693
|
},
|
|
547
694
|
cbk);
|
|
548
695
|
})],
|
|
549
696
|
|
|
697
|
+
// Broadcast the funding transaction when opening on multiple nodes
|
|
698
|
+
broadcastChainTransaction: [
|
|
699
|
+
'fundChannels',
|
|
700
|
+
'fundingPsbt',
|
|
701
|
+
'getFunding',
|
|
702
|
+
'openChannels',
|
|
703
|
+
({fundChannels, fundingPsbt, getFunding, openChannels}, cbk) =>
|
|
704
|
+
{
|
|
705
|
+
const fundingError = getFunding.error || fundingPsbt.error;
|
|
706
|
+
const error = fundChannels.error || fundingError;
|
|
707
|
+
|
|
708
|
+
// Exit early when the opening had an error and broadcasting isn't safe
|
|
709
|
+
if (!!error || !!fundingError) {
|
|
710
|
+
return cbk();
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const [, multiNodeOpening] = openChannels;
|
|
714
|
+
|
|
715
|
+
// Exit early when not opening in multi-node mode
|
|
716
|
+
if (!multiNodeOpening) {
|
|
717
|
+
return cbk();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return broadcastChainTransaction({
|
|
721
|
+
lnd: args.lnd,
|
|
722
|
+
transaction: getFunding.value.transaction,
|
|
723
|
+
},
|
|
724
|
+
(err, res) => {
|
|
725
|
+
if (!!err) {
|
|
726
|
+
return cbk(err);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
args.logger.info({transaction: getFunding.value.transaction});
|
|
730
|
+
|
|
731
|
+
return cbk();
|
|
732
|
+
});
|
|
733
|
+
}],
|
|
734
|
+
|
|
550
735
|
// Cancel pending if there is an error
|
|
551
736
|
cancelPending: [
|
|
552
737
|
'fundChannels',
|
|
@@ -565,15 +750,20 @@ module.exports = (args, cbk) => {
|
|
|
565
750
|
}
|
|
566
751
|
|
|
567
752
|
args.logger.info({
|
|
568
|
-
canceling_pending_channels: openChannels.
|
|
753
|
+
canceling_pending_channels: openChannels.map(({node, pending}) => ({
|
|
754
|
+
node,
|
|
755
|
+
ids: pending.map(n => n.id),
|
|
756
|
+
})),
|
|
569
757
|
});
|
|
570
758
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
759
|
+
return asyncEach(openChannels, ({lnd, pending}, cbk) => {
|
|
760
|
+
return asyncEach(pending => ({id}, cbk) => {
|
|
761
|
+
return cancelPendingChannel({id, lnd}, err => {
|
|
762
|
+
// Ignore errors when trying to cancel a pending channel
|
|
763
|
+
return cbk();
|
|
764
|
+
});
|
|
765
|
+
},
|
|
766
|
+
cbk);
|
|
577
767
|
},
|
|
578
768
|
() => {
|
|
579
769
|
// Return the original error that canceled the finalization
|
|
@@ -592,7 +782,7 @@ module.exports = (args, cbk) => {
|
|
|
592
782
|
return cbk();
|
|
593
783
|
}
|
|
594
784
|
|
|
595
|
-
// Exit early when there
|
|
785
|
+
// Exit early when there are no UTXOs to unlock, like external funding
|
|
596
786
|
if (!isArray(getFunding.inputs)) {
|
|
597
787
|
return cbk(cancelPending);
|
|
598
788
|
}
|
|
@@ -619,28 +809,31 @@ module.exports = (args, cbk) => {
|
|
|
619
809
|
|
|
620
810
|
// Set fee rates
|
|
621
811
|
setFeeRates: [
|
|
622
|
-
'
|
|
812
|
+
'broadcastChainTransaction',
|
|
813
|
+
'cancelLocks',
|
|
623
814
|
'detectFunding',
|
|
624
815
|
'fundChannels',
|
|
625
|
-
|
|
816
|
+
'getLnds',
|
|
817
|
+
'opens',
|
|
818
|
+
({getLnds, opens}, cbk) =>
|
|
626
819
|
{
|
|
627
820
|
// Exit early when not specifying fee rates
|
|
628
821
|
if (args.set_fee_rates.length !== args.public_keys.length) {
|
|
629
822
|
return cbk();
|
|
630
823
|
}
|
|
631
824
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
public_key: args.public_keys[i],
|
|
635
|
-
}));
|
|
825
|
+
return asyncEachSeries(opens, ({channels, node}, cbk) => {
|
|
826
|
+
const {lnd} = getLnds.find(n => n.node === node);
|
|
636
827
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
828
|
+
return asyncEachSeries(channels, (channel, cbk) => {
|
|
829
|
+
return adjustFees({
|
|
830
|
+
lnd,
|
|
831
|
+
fee_rate: channel.rate,
|
|
832
|
+
fs: args.fs,
|
|
833
|
+
logger: args.logger,
|
|
834
|
+
to: [channel.partner_public_key],
|
|
835
|
+
},
|
|
836
|
+
cbk);
|
|
644
837
|
},
|
|
645
838
|
cbk);
|
|
646
839
|
},
|
|
@@ -649,6 +842,7 @@ module.exports = (args, cbk) => {
|
|
|
649
842
|
|
|
650
843
|
// Transaction complete
|
|
651
844
|
completed: [
|
|
845
|
+
'broadcastChainTransaction',
|
|
652
846
|
'cancelPending',
|
|
653
847
|
'fundingPsbt',
|
|
654
848
|
'getFunding',
|
|
@@ -34,6 +34,7 @@ const {isMessageReplyAction} = require('ln-telegram');
|
|
|
34
34
|
const {notifyOfForwards} = require('ln-telegram');
|
|
35
35
|
const {postChainTransaction} = require('ln-telegram');
|
|
36
36
|
const {postClosedMessage} = require('ln-telegram');
|
|
37
|
+
const {postClosingMessage} = require('ln-telegram');
|
|
37
38
|
const {postCreatedTrade} = require('ln-telegram');
|
|
38
39
|
const {postOpenMessage} = require('ln-telegram');
|
|
39
40
|
const {postOpeningMessage} = require('ln-telegram');
|
|
@@ -684,6 +685,20 @@ module.exports = (args, cbk) => {
|
|
|
684
685
|
|
|
685
686
|
subscriptions.push(sub);
|
|
686
687
|
|
|
688
|
+
// Listen for pending closing channel events
|
|
689
|
+
sub.on('closing', update => {
|
|
690
|
+
return postClosingMessage({
|
|
691
|
+
from,
|
|
692
|
+
lnd,
|
|
693
|
+
closing: update.channels,
|
|
694
|
+
id: connectedId,
|
|
695
|
+
nodes: getNodes,
|
|
696
|
+
send: (id, msg, opt) => bot.api.sendMessage(id, msg, opt),
|
|
697
|
+
},
|
|
698
|
+
err => !!err ? logger.error({from, closing_err: err}) : null);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
// Listen for pending opening events
|
|
687
702
|
sub.on('opening', update => {
|
|
688
703
|
return postOpeningMessage({
|
|
689
704
|
from,
|
|
@@ -752,7 +767,6 @@ module.exports = (args, cbk) => {
|
|
|
752
767
|
return notifyOfForwards({
|
|
753
768
|
from,
|
|
754
769
|
lnd,
|
|
755
|
-
request,
|
|
756
770
|
forwards: res.forwards.filter(forward => {
|
|
757
771
|
if (!limits || !limits.min_forward_tokens) {
|
|
758
772
|
return true;
|
|
@@ -761,8 +775,9 @@ module.exports = (args, cbk) => {
|
|
|
761
775
|
return forward.tokens >= limits.min_forward_tokens;
|
|
762
776
|
}),
|
|
763
777
|
id: connectedId,
|
|
764
|
-
key: apiKey.key,
|
|
765
778
|
node: node.public_key,
|
|
779
|
+
nodes: getNodes,
|
|
780
|
+
send: (id, msg, opt) => bot.api.sendMessage(id, msg, opt),
|
|
766
781
|
},
|
|
767
782
|
err => {
|
|
768
783
|
if (!!err) {
|
|
@@ -845,10 +860,8 @@ module.exports = (args, cbk) => {
|
|
|
845
860
|
}
|
|
846
861
|
|
|
847
862
|
return postSettledPayment({
|
|
848
|
-
request,
|
|
849
863
|
from: node.from,
|
|
850
864
|
id: connectedId,
|
|
851
|
-
key: apiKey.key,
|
|
852
865
|
lnd: node.lnd,
|
|
853
866
|
nodes: getNodes.map(n => n.public_key),
|
|
854
867
|
payment: {
|
|
@@ -857,10 +870,9 @@ module.exports = (args, cbk) => {
|
|
|
857
870
|
safe_fee: payment.safe_fee,
|
|
858
871
|
safe_tokens: payment.safe_tokens,
|
|
859
872
|
},
|
|
873
|
+
send: (id, msg, opts) => bot.api.sendMessage(id, msg, opts),
|
|
860
874
|
},
|
|
861
875
|
err => !!err ? logger.error({post_payment_error: err}) : null);
|
|
862
|
-
|
|
863
|
-
return;
|
|
864
876
|
});
|
|
865
877
|
|
|
866
878
|
sub.on('error', err => {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const {addPeer} = require('ln-service');
|
|
2
|
+
const asyncAuto = require('async/auto');
|
|
3
|
+
const asyncEach = require('async/each');
|
|
4
|
+
const asyncRetry = require('async/retry');
|
|
5
|
+
const {createChainAddress} = require('ln-service');
|
|
6
|
+
const {fundPsbt} = require('ln-service');
|
|
7
|
+
const {getChannels} = require('ln-service');
|
|
8
|
+
const {getPendingChannels} = require('ln-service');
|
|
9
|
+
const {getWalletInfo} = require('ln-service');
|
|
10
|
+
const {openChannel} = require('ln-service');
|
|
11
|
+
const {signPsbt} = require('ln-service');
|
|
12
|
+
const {spawnLightningCluster} = require('ln-docker-daemons');
|
|
13
|
+
const {test} = require('@alexbosworth/tap');
|
|
14
|
+
const {Transaction} = require('bitcoinjs-lib');
|
|
15
|
+
|
|
16
|
+
const {openChannels} = require('./../../peers');
|
|
17
|
+
|
|
18
|
+
const count = 100;
|
|
19
|
+
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
20
|
+
const interval = 200;
|
|
21
|
+
const log = () => {};
|
|
22
|
+
const size = 2;
|
|
23
|
+
const times = 1000;
|
|
24
|
+
|
|
25
|
+
// Opening channels should open channels with specified nodes
|
|
26
|
+
test(`Open channels`, async ({end, equal, strictSame}) => {
|
|
27
|
+
const {kill, nodes} = await spawnLightningCluster({size});
|
|
28
|
+
|
|
29
|
+
const [{generate, lnd}, target] = nodes;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
await generate({count});
|
|
33
|
+
|
|
34
|
+
await asyncRetry({interval, times}, async () => {
|
|
35
|
+
await addPeer({lnd, public_key: target.id, socket: target.socket});
|
|
36
|
+
|
|
37
|
+
await asyncEach(nodes, async ({lnd}) => {
|
|
38
|
+
const chain = await getWalletInfo({lnd});
|
|
39
|
+
|
|
40
|
+
if (!chain.is_synced_to_chain || !chain.is_synced_to_graph) {
|
|
41
|
+
throw new Error('WaitingForSync');
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const {address} = await createChainAddress({lnd});
|
|
47
|
+
|
|
48
|
+
await delay(4000);
|
|
49
|
+
|
|
50
|
+
// Open a single channel from a single node
|
|
51
|
+
await asyncAuto({
|
|
52
|
+
// Open channel
|
|
53
|
+
propose: async () => {
|
|
54
|
+
await openChannels({
|
|
55
|
+
lnd,
|
|
56
|
+
ask: async (args, cbk) => {
|
|
57
|
+
if (args.name === 'internal') {
|
|
58
|
+
return cbk({internal: false});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (args.name === 'fund') {
|
|
62
|
+
const address = args.message.split(' ')[9];
|
|
63
|
+
|
|
64
|
+
const {psbt} = await fundPsbt({
|
|
65
|
+
lnd,
|
|
66
|
+
outputs: [{address, tokens: 6e6}],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const signed = await signPsbt({lnd, psbt});
|
|
70
|
+
|
|
71
|
+
return cbk({fund: signed.psbt});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
throw new Error('UnrecognizedParameter');
|
|
75
|
+
},
|
|
76
|
+
capacities: ['6*m'],
|
|
77
|
+
cooperative_close_addresses: [address],
|
|
78
|
+
fs: {getFile: () => {}},
|
|
79
|
+
gives: [1e5],
|
|
80
|
+
logger: {info: log, error: log},
|
|
81
|
+
opening_nodes: [],
|
|
82
|
+
public_keys: [target.id],
|
|
83
|
+
request: () => {},
|
|
84
|
+
set_fee_rates: [],
|
|
85
|
+
types: [],
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// Generate blocks until the channel confirms
|
|
90
|
+
generate: async () => {
|
|
91
|
+
return await asyncRetry({interval, times}, async () => {
|
|
92
|
+
await generate({});
|
|
93
|
+
|
|
94
|
+
const {channels} = await getChannels({lnd});
|
|
95
|
+
|
|
96
|
+
if (!channels.length) {
|
|
97
|
+
throw new Error('Expected Channels');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const [channel] = channels;
|
|
101
|
+
|
|
102
|
+
equal(channel.remote_balance, 1e5, 'Gift balance is reflected');
|
|
103
|
+
equal(channel.capacity, 6e6, 'Channel capacity is set');
|
|
104
|
+
equal(channel.cooperative_close_address, address, 'Coop address');
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
} catch (err) {
|
|
109
|
+
equal(err, null, 'Expected no error');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await kill({});
|
|
113
|
+
});
|
|
@@ -8,6 +8,8 @@ const makeArgs = overrides => {
|
|
|
8
8
|
capacities: [2],
|
|
9
9
|
gives: ['1'],
|
|
10
10
|
nodes: [Buffer.alloc(33, 3).toString('hex')],
|
|
11
|
+
rates: [],
|
|
12
|
+
saved: [],
|
|
11
13
|
types: ['private'],
|
|
12
14
|
};
|
|
13
15
|
|
|
@@ -21,12 +23,16 @@ const tests = [
|
|
|
21
23
|
args: makeArgs({}),
|
|
22
24
|
description: 'Arguments are mapped to channel details',
|
|
23
25
|
expected: {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
opens: [{
|
|
27
|
+
channels: [{
|
|
28
|
+
capacity: 2,
|
|
29
|
+
cooperative_close_address: 'address',
|
|
30
|
+
give_tokens: 1,
|
|
31
|
+
is_private: true,
|
|
32
|
+
node: undefined,
|
|
33
|
+
partner_public_key: Buffer.alloc(33, 3).toString('hex'),
|
|
34
|
+
rate: undefined,
|
|
35
|
+
}],
|
|
30
36
|
}],
|
|
31
37
|
},
|
|
32
38
|
},
|
|
@@ -39,15 +45,59 @@ const tests = [
|
|
|
39
45
|
}),
|
|
40
46
|
description: 'Remove optional arguments',
|
|
41
47
|
expected: {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
opens: [{
|
|
49
|
+
channels: [{
|
|
50
|
+
capacity: 5000000,
|
|
51
|
+
cooperative_close_address: undefined,
|
|
52
|
+
give_tokens: undefined,
|
|
53
|
+
is_private: false,
|
|
54
|
+
node: undefined,
|
|
55
|
+
partner_public_key: Buffer.alloc(33, 3).toString('hex'),
|
|
56
|
+
rate: undefined,
|
|
57
|
+
}],
|
|
48
58
|
}],
|
|
49
59
|
},
|
|
50
60
|
},
|
|
61
|
+
{
|
|
62
|
+
args: makeArgs({
|
|
63
|
+
addresses: ['coopCloseAddressNodeA', 'coopCloseAddressNodeB'],
|
|
64
|
+
capacities: [1, 2],
|
|
65
|
+
gives: [3, 4],
|
|
66
|
+
nodes: ['remoteNodeA', 'remoteNodeB'],
|
|
67
|
+
rates: ['1', '2'],
|
|
68
|
+
saved: ['savedA', 'savedB'],
|
|
69
|
+
types: ['private', 'public'],
|
|
70
|
+
}),
|
|
71
|
+
description: 'Two nodes are batch opening',
|
|
72
|
+
expected: {
|
|
73
|
+
opens: [
|
|
74
|
+
{
|
|
75
|
+
channels: [{
|
|
76
|
+
capacity: 1,
|
|
77
|
+
cooperative_close_address: 'coopCloseAddressNodeA',
|
|
78
|
+
give_tokens: 3,
|
|
79
|
+
is_private: true,
|
|
80
|
+
node: 'savedA',
|
|
81
|
+
partner_public_key: 'remoteNodeA',
|
|
82
|
+
rate: '1',
|
|
83
|
+
}],
|
|
84
|
+
node: 'savedA',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
channels: [{
|
|
88
|
+
capacity: 2,
|
|
89
|
+
cooperative_close_address: 'coopCloseAddressNodeB',
|
|
90
|
+
give_tokens: 4,
|
|
91
|
+
is_private: false,
|
|
92
|
+
node: 'savedB',
|
|
93
|
+
partner_public_key: 'remoteNodeB',
|
|
94
|
+
rate: '2',
|
|
95
|
+
}],
|
|
96
|
+
node: 'savedB',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
51
101
|
];
|
|
52
102
|
|
|
53
103
|
tests.forEach(({args, description, error, expected}) => {
|