paid-services 3.10.0 → 3.11.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 +4 -0
- package/capacity/accept_capacity_change.js +7 -1
- package/capacity/ask_for_change_details.js +101 -19
- package/capacity/ask_to_select_change.js +109 -0
- package/capacity/capacity_change_requests.js +2 -0
- package/capacity/change_channel_capacity.js +44 -43
- package/capacity/describe_capacity_change.js +38 -0
- package/capacity/encode_change_request.js +17 -3
- package/capacity/get_capacity_change_requests.js +1 -0
- package/capacity/get_capacity_replacement.js +6 -1
- package/capacity/get_replacement_signature.js +8 -3
- package/capacity/initiate_capacity_change.js +18 -3
- package/capacity/parse_capacity_change_request.js +30 -8
- package/capacity/propose_capacity_change.js +8 -2
- package/package.json +2 -2
- package/test/capacity/test_capacity_change_requests.js +1 -0
- package/test/capacity/test_encode_change_request.js +4 -0
- package/test/integration/test_accept_capacity_change.js +2 -0
- package/test/integration/test_change_channel_capacity.js +2 -0
- package/test/integration/test_decrease_channel_capacity.js +2 -0
- package/test/integration/test_increase_channel_capacity.js +2 -0
- package/test/integration/test_move_channel_capacity.js +230 -0
- package/test/integration/test_split_channel_capacity.js +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -32,6 +32,7 @@ const openTxType = '1';
|
|
|
32
32
|
[increase]: <Increase Tokens Number>
|
|
33
33
|
lnd: <Authenticated LND API Object>
|
|
34
34
|
logger: <Winston Logger Object>
|
|
35
|
+
to: <Expect Channel from Public Key Hex String>
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
@returns via cbk or Promise
|
|
@@ -43,7 +44,7 @@ const openTxType = '1';
|
|
|
43
44
|
transaction_vout: <Replacement Channel Transaction Output Index Number>
|
|
44
45
|
}
|
|
45
46
|
*/
|
|
46
|
-
module.exports = ({channel, from, id, increase, lnd, logger}, cbk) => {
|
|
47
|
+
module.exports = ({channel, from, id, increase, lnd, logger, to}, cbk) => {
|
|
47
48
|
return new Promise((resolve, reject) => {
|
|
48
49
|
return asyncAuto({
|
|
49
50
|
// Check arguments
|
|
@@ -68,6 +69,10 @@ module.exports = ({channel, from, id, increase, lnd, logger}, cbk) => {
|
|
|
68
69
|
return cbk([400, 'ExpectedLoggerObjectToAcceptCapacityChange']);
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
if (!to) {
|
|
73
|
+
return cbk([400, 'ExpectedToPublicKeyToAcceptCapacityChange']);
|
|
74
|
+
}
|
|
75
|
+
|
|
71
76
|
return cbk();
|
|
72
77
|
},
|
|
73
78
|
|
|
@@ -219,6 +224,7 @@ module.exports = ({channel, from, id, increase, lnd, logger}, cbk) => {
|
|
|
219
224
|
channel,
|
|
220
225
|
lnd,
|
|
221
226
|
output,
|
|
227
|
+
to,
|
|
222
228
|
id: parameters.id,
|
|
223
229
|
unsigned: parseSignCapacityRequest(parameters).unsigned,
|
|
224
230
|
vout: parameters.vout,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
2
|
const asyncUntil = require('async/until');
|
|
3
|
+
const {connectPeer} = require('ln-sync');
|
|
3
4
|
const {findKey} = require('ln-sync');
|
|
4
5
|
const {getChainFeeRate} = require('ln-service');
|
|
5
6
|
const {getChainTransactions} = require('ln-service');
|
|
6
7
|
const {getChannel} = require('ln-service');
|
|
7
8
|
const {getChannels} = require('ln-service');
|
|
9
|
+
const {getNodeAlias} = require('ln-sync');
|
|
8
10
|
const {getPeers} = require('ln-service');
|
|
9
11
|
const {returnResult} = require('asyncjs-util');
|
|
10
12
|
const {sendMessageToPeer} = require('ln-service');
|
|
@@ -16,6 +18,7 @@ const feeForReplacement = require('./fee_for_replacement');
|
|
|
16
18
|
const {ceil} = Math;
|
|
17
19
|
const dust = 550;
|
|
18
20
|
const dustBuffer = 750;
|
|
21
|
+
const {isArray} = Array;
|
|
19
22
|
const isNumber = n => !isNaN(n);
|
|
20
23
|
const largeChannelsBit = 19;
|
|
21
24
|
const maxSats = 21e6 * 1e8;
|
|
@@ -40,6 +43,11 @@ const weightBuffer = 150;
|
|
|
40
43
|
ask: <Ask Function>
|
|
41
44
|
id: <Request Identifier Hex String>
|
|
42
45
|
lnd: <Authenticated LND API Object>
|
|
46
|
+
nodes: [{
|
|
47
|
+
lnd: <Potential Move Node LND API Object>
|
|
48
|
+
node_name: <Potential Move Node Name String>
|
|
49
|
+
public_key: <Potential Move Node Public Key Hex String>
|
|
50
|
+
}]
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
@returns via cbk or Promise
|
|
@@ -59,6 +67,8 @@ const weightBuffer = 150;
|
|
|
59
67
|
id: <Standard Format Channel Id String>
|
|
60
68
|
increase: <Channel Capacity Increase Tokens Number>
|
|
61
69
|
is_private: <Channel is Private Bool>
|
|
70
|
+
[open_from]: <Open Replacement Channel From Node with Public Key String>
|
|
71
|
+
open_lnd: <Open Replacement Channel with LND API Object>
|
|
62
72
|
[open_transaction]: <Channel Open Transaction Hex String>
|
|
63
73
|
partner_csv_delay: <Peer CSV Delay Number>
|
|
64
74
|
partner_public_key: <Node to Ask For Capacity Change Public Key Hex String>
|
|
@@ -70,7 +80,7 @@ const weightBuffer = 150;
|
|
|
70
80
|
transaction_id: <Channel Funding Transaction Id Hex String>
|
|
71
81
|
}
|
|
72
82
|
*/
|
|
73
|
-
module.exports = ({ask, id, lnd}, cbk) => {
|
|
83
|
+
module.exports = ({ask, id, lnd, nodes}, cbk) => {
|
|
74
84
|
return new Promise((resolve, reject) => {
|
|
75
85
|
return asyncAuto({
|
|
76
86
|
// Check arguments
|
|
@@ -87,6 +97,10 @@ module.exports = ({ask, id, lnd}, cbk) => {
|
|
|
87
97
|
return cbk([400, 'ExpectedLndToAskForChangeDetails']);
|
|
88
98
|
}
|
|
89
99
|
|
|
100
|
+
if (!isArray(nodes)) {
|
|
101
|
+
return cbk([400, 'ExpectedArrayOfPotentialMoveNodesToAskForChange']);
|
|
102
|
+
}
|
|
103
|
+
|
|
90
104
|
return cbk();
|
|
91
105
|
},
|
|
92
106
|
|
|
@@ -112,7 +126,7 @@ module.exports = ({ask, id, lnd}, cbk) => {
|
|
|
112
126
|
return getChainFeeRate({lnd, confirmation_target: slowTarget}, cbk);
|
|
113
127
|
}],
|
|
114
128
|
|
|
115
|
-
// Find the public key or alias entered
|
|
129
|
+
// Find the node with the public key or alias entered
|
|
116
130
|
findKey: [
|
|
117
131
|
'askForPeer',
|
|
118
132
|
'getChannels',
|
|
@@ -126,6 +140,11 @@ module.exports = ({ask, id, lnd}, cbk) => {
|
|
|
126
140
|
cbk);
|
|
127
141
|
}],
|
|
128
142
|
|
|
143
|
+
// Get the alias of the peer
|
|
144
|
+
getAlias: ['findKey', ({findKey}, cbk) => {
|
|
145
|
+
return getNodeAlias({lnd, id: findKey.public_key}, cbk);
|
|
146
|
+
}],
|
|
147
|
+
|
|
129
148
|
// Send a test message to the selected peer to confirm they are connected
|
|
130
149
|
testPeer: ['findKey', 'getChannels', ({findKey, getChannels}, cbk) => {
|
|
131
150
|
const hasPeer = !!getChannels.channels.find(channel => {
|
|
@@ -190,7 +209,7 @@ module.exports = ({ask, id, lnd}, cbk) => {
|
|
|
190
209
|
return ask({
|
|
191
210
|
choices,
|
|
192
211
|
loop: false,
|
|
193
|
-
message: 'Channel to change
|
|
212
|
+
message: 'Channel to change?',
|
|
194
213
|
name: 'id',
|
|
195
214
|
type: 'list',
|
|
196
215
|
},
|
|
@@ -294,20 +313,32 @@ module.exports = ({ask, id, lnd}, cbk) => {
|
|
|
294
313
|
}],
|
|
295
314
|
|
|
296
315
|
// Choose whether to add or remove coins
|
|
297
|
-
askForDirection: ['channel', ({channel}, cbk) => {
|
|
316
|
+
askForDirection: ['channel', 'getAlias', ({channel, getAlias}, cbk) => {
|
|
317
|
+
const {alias} = getAlias;
|
|
318
|
+
|
|
319
|
+
const choices = [
|
|
320
|
+
{
|
|
321
|
+
disabled: !channel.max_increase_tokens,
|
|
322
|
+
name: `Increase capacity (currently ${channel.capacity})`,
|
|
323
|
+
value: 'increase',
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
disabled: !channel.max_decrease_tokens,
|
|
327
|
+
name: `Decrease capacity (limit ${channel.max_decrease_tokens})`,
|
|
328
|
+
value: 'decrease',
|
|
329
|
+
},
|
|
330
|
+
];
|
|
331
|
+
|
|
332
|
+
if (!!nodes.filter(n => n.public_key !== getAlias.id).length) {
|
|
333
|
+
choices.push({
|
|
334
|
+
disabled: !channel.max_decrease_tokens,
|
|
335
|
+
name: `Move the channel with ${alias} to another of your nodes?`,
|
|
336
|
+
value: 'migrate',
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
298
340
|
return ask({
|
|
299
|
-
choices
|
|
300
|
-
{
|
|
301
|
-
disabled: !channel.max_increase_tokens,
|
|
302
|
-
name: `Increase capacity (currently ${channel.capacity})`,
|
|
303
|
-
value: 'increase',
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
disabled: !channel.max_decrease_tokens,
|
|
307
|
-
name: `Decrease capacity (limit ${channel.max_decrease_tokens})`,
|
|
308
|
-
value: 'decrease',
|
|
309
|
-
},
|
|
310
|
-
],
|
|
341
|
+
choices,
|
|
311
342
|
message: 'How do you want to change the channel capacity?',
|
|
312
343
|
name: 'direction',
|
|
313
344
|
type: 'list',
|
|
@@ -315,6 +346,51 @@ module.exports = ({ask, id, lnd}, cbk) => {
|
|
|
315
346
|
({direction}) => cbk(null, direction));
|
|
316
347
|
}],
|
|
317
348
|
|
|
349
|
+
// Ask for migration
|
|
350
|
+
askForMigration: [
|
|
351
|
+
'askForDirection',
|
|
352
|
+
'channel',
|
|
353
|
+
({askForDirection, channel}, cbk) =>
|
|
354
|
+
{
|
|
355
|
+
// Exit early if not decrease or no saved nodes
|
|
356
|
+
if (askForDirection !== 'migrate') {
|
|
357
|
+
return cbk();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const peer = channel.partner_public_key;
|
|
361
|
+
|
|
362
|
+
const potentialNodes = nodes.filter(n => n.public_key !== peer);
|
|
363
|
+
|
|
364
|
+
return ask({
|
|
365
|
+
choices: potentialNodes.map((node, i) => ({
|
|
366
|
+
name: `${node.node_name} ${node.public_key}`,
|
|
367
|
+
value: i,
|
|
368
|
+
})),
|
|
369
|
+
message: 'Move channel to?',
|
|
370
|
+
name: 'migration',
|
|
371
|
+
type: 'list',
|
|
372
|
+
},
|
|
373
|
+
({migration}) => cbk(null, potentialNodes[migration]));
|
|
374
|
+
}],
|
|
375
|
+
|
|
376
|
+
// Add peer from migrating node
|
|
377
|
+
connect: [
|
|
378
|
+
'askForMigration',
|
|
379
|
+
'findKey',
|
|
380
|
+
({askForMigration, findKey}, cbk) =>
|
|
381
|
+
{
|
|
382
|
+
// Exit early if its there is no migration
|
|
383
|
+
if (!askForMigration) {
|
|
384
|
+
return cbk();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return connectPeer({
|
|
388
|
+
id: findKey.public_key,
|
|
389
|
+
lnd: askForMigration.lnd,
|
|
390
|
+
},
|
|
391
|
+
cbk);
|
|
392
|
+
}],
|
|
393
|
+
|
|
318
394
|
// Ask for how much to decrease
|
|
319
395
|
askForDecrease: [
|
|
320
396
|
'askForDirection',
|
|
@@ -400,6 +476,7 @@ module.exports = ({ask, id, lnd}, cbk) => {
|
|
|
400
476
|
askForPublicPrivate: [
|
|
401
477
|
'askForDecrease',
|
|
402
478
|
'askForIncrease',
|
|
479
|
+
'askForMigration',
|
|
403
480
|
'channel',
|
|
404
481
|
({channel}, cbk) =>
|
|
405
482
|
{
|
|
@@ -452,12 +529,10 @@ module.exports = ({ask, id, lnd}, cbk) => {
|
|
|
452
529
|
{
|
|
453
530
|
const {fee} = estimateChainFee;
|
|
454
531
|
|
|
455
|
-
const pay = `Pay estimated on-chain channel replacement fee: ${fee}`;
|
|
456
|
-
|
|
457
532
|
return ask({
|
|
458
533
|
type: 'confirm',
|
|
459
534
|
name: 'proceed',
|
|
460
|
-
message:
|
|
535
|
+
message: `Pay estimated channel replacement chain-fee: ${fee}?`,
|
|
461
536
|
},
|
|
462
537
|
({proceed}) => cbk(null, proceed));
|
|
463
538
|
}],
|
|
@@ -466,16 +541,20 @@ module.exports = ({ask, id, lnd}, cbk) => {
|
|
|
466
541
|
details: [
|
|
467
542
|
'askForDecrease',
|
|
468
543
|
'askForIncrease',
|
|
544
|
+
'askForMigration',
|
|
469
545
|
'askForPublicPrivate',
|
|
470
546
|
'channel',
|
|
547
|
+
'connect',
|
|
471
548
|
'confirmFeeEstimate',
|
|
472
549
|
'estimateChainFee',
|
|
473
550
|
'getTx',
|
|
474
551
|
({
|
|
475
552
|
askForDecrease,
|
|
476
553
|
askForIncrease,
|
|
554
|
+
askForMigration,
|
|
477
555
|
askForPublicPrivate,
|
|
478
556
|
channel,
|
|
557
|
+
connect,
|
|
479
558
|
confirmFeeEstimate,
|
|
480
559
|
estimateChainFee,
|
|
481
560
|
getTx,
|
|
@@ -509,6 +588,7 @@ module.exports = ({ask, id, lnd}, cbk) => {
|
|
|
509
588
|
channel: channel.id,
|
|
510
589
|
decrease: !!askForDecrease ? sumOfTokens(askForDecrease) : undefined,
|
|
511
590
|
increase: askForIncrease,
|
|
591
|
+
to: !!askForMigration ? askForMigration.public_key : undefined,
|
|
512
592
|
type: askForPublicPrivate.is_private ? privateType : publicType,
|
|
513
593
|
});
|
|
514
594
|
|
|
@@ -524,6 +604,8 @@ module.exports = ({ask, id, lnd}, cbk) => {
|
|
|
524
604
|
id: channel.id,
|
|
525
605
|
increase: askForIncrease,
|
|
526
606
|
is_private: askForPublicPrivate.is_private,
|
|
607
|
+
open_from: !!askForMigration ? askForMigration.public_key : null,
|
|
608
|
+
open_lnd: !!askForMigration ? askForMigration.lnd : lnd,
|
|
527
609
|
open_transaction: channel.open_transaction,
|
|
528
610
|
partner_csv_delay: channel.partner_csv_delay,
|
|
529
611
|
partner_public_key: channel.partner_public_key,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {getNodeAlias} = require('ln-sync');
|
|
3
|
+
const {returnResult} = require('asyncjs-util');
|
|
4
|
+
|
|
5
|
+
const describeCapacityChange = require('./describe_capacity_change');
|
|
6
|
+
|
|
7
|
+
/** Ask to select a change
|
|
8
|
+
|
|
9
|
+
{
|
|
10
|
+
ask: <Inquirer Ask Function>
|
|
11
|
+
[address]: <Cooperative Close Address String>
|
|
12
|
+
capacity: <Channel Capacity Tokens Number>
|
|
13
|
+
channel: <Change Channel Id String>
|
|
14
|
+
[decrease]: <Capacity Change Decrease Tokens Number>
|
|
15
|
+
from_id: <Peer Public Key Hex String>
|
|
16
|
+
[increase]: <Capacity Change Increase Tokens Number>
|
|
17
|
+
lnd: <Authenticated LND API Object>
|
|
18
|
+
[to_id]: <Node Public Key Hex String>
|
|
19
|
+
[type]: <Channel Type String>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@returns via cbk or Promise
|
|
23
|
+
{
|
|
24
|
+
is_selected: <Request to Change Channel is Selected Bool>
|
|
25
|
+
}
|
|
26
|
+
*/
|
|
27
|
+
module.exports = (args, cbk) => {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
return asyncAuto({
|
|
30
|
+
// Check arguments
|
|
31
|
+
validate: cbk => {
|
|
32
|
+
if (!args.ask) {
|
|
33
|
+
return cbk([400, 'ExpectedAskFunctionToAskToSelectChange']);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!args.capacity) {
|
|
37
|
+
return cbk([400, 'ExpectedChannelCapacityToAskToSelectChange']);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!args.channel) {
|
|
41
|
+
return cbk([400, 'ExpectedChannelIdToAskToSelectAChangeFor']);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!args.from_id) {
|
|
45
|
+
return cbk([400, 'ExpectedChannelPartnerPublicKeyToSelectChange']);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!args.lnd) {
|
|
49
|
+
return cbk([400, 'ExpectedLndApiToAskToSelectChange']);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return cbk();
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// Get the peer alias
|
|
56
|
+
getPeerAlias: ['validate', ({}, cbk) => {
|
|
57
|
+
return getNodeAlias({id: args.from_id, lnd: args.lnd}, cbk);
|
|
58
|
+
}],
|
|
59
|
+
|
|
60
|
+
// Get the alias of the node to move to
|
|
61
|
+
getNodeAlias: ['validate', ({}, cbk) => {
|
|
62
|
+
// Exit early when there is no new node to move to
|
|
63
|
+
if (!args.to_id) {
|
|
64
|
+
return cbk(null, {});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return getNodeAlias({id: args.to_id, lnd: args.lnd}, cbk);
|
|
68
|
+
}],
|
|
69
|
+
|
|
70
|
+
// Confirm acceptance of the change
|
|
71
|
+
confirm: [
|
|
72
|
+
'getNodeAlias',
|
|
73
|
+
'getPeerAlias',
|
|
74
|
+
({getNodeAlias, getPeerAlias}, cbk) =>
|
|
75
|
+
{
|
|
76
|
+
const message = describeCapacityChange({
|
|
77
|
+
capacity: args.capacity,
|
|
78
|
+
channel: args.channel,
|
|
79
|
+
decrease: args.decrease,
|
|
80
|
+
from_alias: getPeerAlias.alias,
|
|
81
|
+
from_id: args.from_id,
|
|
82
|
+
increase: args.increase,
|
|
83
|
+
to_alias: getNodeAlias.alias,
|
|
84
|
+
to_id: args.to_id,
|
|
85
|
+
type: args.type,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return args.ask({
|
|
89
|
+
message,
|
|
90
|
+
name: 'accept',
|
|
91
|
+
type: 'confirm',
|
|
92
|
+
},
|
|
93
|
+
({accept}) => {
|
|
94
|
+
if (!accept) {
|
|
95
|
+
return cbk(null, {is_selected: false});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Fail changing when cooperative_close_address is set
|
|
99
|
+
if (!!args.address && !args.increase) {
|
|
100
|
+
return cbk([400, 'ChannelHasLockedCoopCloseAddressSet']);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return cbk(null, {is_selected: true});
|
|
104
|
+
});
|
|
105
|
+
}],
|
|
106
|
+
},
|
|
107
|
+
returnResult({reject, resolve, of: 'confirm'}, cbk));
|
|
108
|
+
});
|
|
109
|
+
};
|
|
@@ -29,6 +29,7 @@ const privateAsType = isPrivate => isPrivate ? 1 : 0;
|
|
|
29
29
|
from: <From Node Public Key Id Hex String>
|
|
30
30
|
id: <Change Request Id Hex String>
|
|
31
31
|
[increase]: <Add Capacity Tokens Number>
|
|
32
|
+
[to]: <Switch Channel to Node with Identity Public Key Hex String>
|
|
32
33
|
[type]: <Change Channel Channel Type Flags Number>
|
|
33
34
|
}]
|
|
34
35
|
}
|
|
@@ -77,6 +78,7 @@ module.exports = ({channels, requests}) => {
|
|
|
77
78
|
from: channel.partner_public_key,
|
|
78
79
|
id: request.id,
|
|
79
80
|
increase: request.increase,
|
|
81
|
+
to: request.to,
|
|
80
82
|
type: type ? request.type : undefined,
|
|
81
83
|
};
|
|
82
84
|
});
|
|
@@ -2,20 +2,17 @@ const asyncAuto = require('async/auto');
|
|
|
2
2
|
const asyncDetectSeries = require('async/detectSeries');
|
|
3
3
|
const asyncRetry = require('async/retry');
|
|
4
4
|
const {getIdentity} = require('ln-service');
|
|
5
|
-
const {getNodeAlias} = require('ln-sync');
|
|
6
5
|
const {returnResult} = require('asyncjs-util');
|
|
7
6
|
const {updateChannelFee} = require('ln-sync');
|
|
8
7
|
|
|
9
8
|
const acceptCapacityChange = require('./accept_capacity_change');
|
|
9
|
+
const askToSelectChange = require('./ask_to_select_change');
|
|
10
10
|
const getCapacityChangeRequests = require('./get_capacity_change_requests');
|
|
11
11
|
const initiateCapacityChange = require('./initiate_capacity_change');
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const peerName = ({alias, id}) => `${alias} ${id.substring(0, 8)}`.trim();
|
|
13
|
+
const interval = 1000 * 10;
|
|
14
|
+
const {isArray} = Array;
|
|
16
15
|
const times = 6 * 60 * 6;
|
|
17
|
-
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
18
|
-
const waitForCloseInterval = 1000 * 3;
|
|
19
16
|
|
|
20
17
|
/** Change a channel's capacity
|
|
21
18
|
|
|
@@ -24,9 +21,14 @@ const waitForCloseInterval = 1000 * 3;
|
|
|
24
21
|
[delay]: <Wait Time For Incoming Capacity Requests Milliseconds Number>
|
|
25
22
|
lnd: <Authenticated LND API Object>
|
|
26
23
|
logger: <Winston Logger Object>
|
|
24
|
+
[nodes]: [{
|
|
25
|
+
lnd: <Authenticated LND API Object>
|
|
26
|
+
named: <Node Named String.
|
|
27
|
+
public_key: <Node Identity Public Key Hex String>
|
|
28
|
+
}]
|
|
27
29
|
}
|
|
28
30
|
*/
|
|
29
|
-
module.exports = ({ask, delay, lnd, logger}, cbk) => {
|
|
31
|
+
module.exports = ({ask, delay, lnd, logger, nodes}, cbk) => {
|
|
30
32
|
return new Promise((resolve, reject) => {
|
|
31
33
|
return asyncAuto({
|
|
32
34
|
// Check arguments
|
|
@@ -43,6 +45,10 @@ module.exports = ({ask, delay, lnd, logger}, cbk) => {
|
|
|
43
45
|
return cbk([400, 'ExpectedLoggerObjectToChangeCapacity']);
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
if (!!nodes && !isArray(nodes)) {
|
|
49
|
+
return cbk([400, 'ExpectedArrayOfControlledNodesToChangeCapacity']);
|
|
50
|
+
}
|
|
51
|
+
|
|
46
52
|
return cbk();
|
|
47
53
|
},
|
|
48
54
|
|
|
@@ -83,42 +89,24 @@ module.exports = ({ask, delay, lnd, logger}, cbk) => {
|
|
|
83
89
|
({waitForProposal}, cbk) =>
|
|
84
90
|
{
|
|
85
91
|
return asyncDetectSeries(waitForProposal.requests, (request, cbk) => {
|
|
86
|
-
return
|
|
92
|
+
return askToSelectChange({
|
|
93
|
+
ask,
|
|
94
|
+
lnd,
|
|
95
|
+
address: request.address,
|
|
96
|
+
capacity: request.capacity,
|
|
97
|
+
channel: request.channel,
|
|
98
|
+
decrease: request.decrease,
|
|
99
|
+
from_id: request.from,
|
|
100
|
+
increase: request.increase,
|
|
101
|
+
to_id: request.to,
|
|
102
|
+
type: request.type,
|
|
103
|
+
},
|
|
104
|
+
(err, res) => {
|
|
87
105
|
if (!!err) {
|
|
88
106
|
return cbk(err);
|
|
89
107
|
}
|
|
90
108
|
|
|
91
|
-
|
|
92
|
-
const delta = request.decrease || request.increase;
|
|
93
|
-
const hasType = request.type !== undefined;
|
|
94
|
-
const id = request.channel;
|
|
95
|
-
const peer = peerName(res);
|
|
96
|
-
const size = tokensAsBigUnit(request.capacity);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const action = `${change} capacity ${size} channel ${id}`;
|
|
100
|
-
const by = !!delta ? ` by ${tokensAsBigUnit(delta)}` : '';
|
|
101
|
-
const type = hasType ? describeType(request.type) : '';
|
|
102
|
-
|
|
103
|
-
const changeType = hasType ? ` and make channel ${type}` : '';
|
|
104
|
-
|
|
105
|
-
return ask({
|
|
106
|
-
type: 'confirm',
|
|
107
|
-
name: 'accept',
|
|
108
|
-
message: `${action} with ${peer}${by}${changeType}?`,
|
|
109
|
-
},
|
|
110
|
-
({accept}) => {
|
|
111
|
-
if (!accept) {
|
|
112
|
-
return cbk(null, false);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Fail changing when cooperative_close_address is set
|
|
116
|
-
if (!!request.address && !!request.increase) {
|
|
117
|
-
return cbk([400, 'ChannelHasLockedCoopCloseAddressSet']);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return cbk(null, true);
|
|
121
|
-
});
|
|
109
|
+
return cbk(null, res.is_selected);
|
|
122
110
|
});
|
|
123
111
|
},
|
|
124
112
|
cbk);
|
|
@@ -138,17 +126,30 @@ module.exports = ({ask, delay, lnd, logger}, cbk) => {
|
|
|
138
126
|
from: askToSelectChange.from,
|
|
139
127
|
id: askToSelectChange.id,
|
|
140
128
|
increase: askToSelectChange.increase,
|
|
129
|
+
to: askToSelectChange.to || askToSelectChange.from,
|
|
141
130
|
},
|
|
142
131
|
cbk);
|
|
143
132
|
}],
|
|
144
133
|
|
|
145
134
|
// Initiate a capacity change
|
|
146
|
-
initiateChange: [
|
|
135
|
+
initiateChange: [
|
|
136
|
+
'askToSelectChange',
|
|
137
|
+
'getIdentity',
|
|
138
|
+
({askToSelectChange, getIdentity}, cbk) =>
|
|
139
|
+
{
|
|
147
140
|
if (!!askToSelectChange) {
|
|
148
141
|
return cbk();
|
|
149
142
|
}
|
|
150
143
|
|
|
151
|
-
return initiateCapacityChange({
|
|
144
|
+
return initiateCapacityChange({
|
|
145
|
+
ask,
|
|
146
|
+
lnd,
|
|
147
|
+
logger,
|
|
148
|
+
nodes: (nodes || []).filter(node => {
|
|
149
|
+
return node.public_key !== getIdentity.public_key;
|
|
150
|
+
}),
|
|
151
|
+
},
|
|
152
|
+
cbk);
|
|
152
153
|
}],
|
|
153
154
|
|
|
154
155
|
// Reset the channel routing policies
|
|
@@ -164,11 +165,11 @@ module.exports = ({ask, delay, lnd, logger}, cbk) => {
|
|
|
164
165
|
|
|
165
166
|
return asyncRetry({interval, times}, cbk => {
|
|
166
167
|
return updateChannelFee({
|
|
167
|
-
lnd,
|
|
168
168
|
base_fee_mtokens: policy.base_fee_mtokens,
|
|
169
169
|
cltv_delta: policy.cltv_delta,
|
|
170
170
|
fee_rate: policy.fee_rate,
|
|
171
|
-
from: getIdentity.public_key,
|
|
171
|
+
from: policy.open_from || getIdentity.public_key,
|
|
172
|
+
lnd: policy.open_lnd || lnd,
|
|
172
173
|
transaction_id: policy.transaction_id,
|
|
173
174
|
transaction_vout: policy.transaction_vout,
|
|
174
175
|
},
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const describeType = type => !(type & 1) ? 'private' : 'public';
|
|
2
|
+
const peerName = ({alias, id}) => `${alias} ${id.substring(0, 8)}`.trim();
|
|
3
|
+
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
4
|
+
|
|
5
|
+
/** Describe a capacity change
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
capacity: <Channel Capacity Tokens Number>
|
|
9
|
+
channel: <Change Channel Id String>
|
|
10
|
+
[decrease]: <Capacity Change Decrease Tokens Number>
|
|
11
|
+
from_alias: <Peer Alias String>
|
|
12
|
+
from_id: <Peer Public Key Hex String>
|
|
13
|
+
[increase]: <Capacity Change Increase Tokens Number>
|
|
14
|
+
[to_alias]: <Node Alias String>
|
|
15
|
+
[to_id]: <Node Public Key Hex String>
|
|
16
|
+
[type]: <Channel Type String>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@returns
|
|
20
|
+
<Capacity Change Description String>
|
|
21
|
+
*/
|
|
22
|
+
module.exports = args => {
|
|
23
|
+
const change = !!args.increase ? 'Increase' : 'Decrease';
|
|
24
|
+
const delta = args.decrease || args.increase;
|
|
25
|
+
const hasType = args.type !== undefined;
|
|
26
|
+
const newPeer = peerName({alias: args.to_alias, id: args.to_id || ''});
|
|
27
|
+
const peer = peerName({alias: args.from_alias, id: args.from_id});
|
|
28
|
+
const size = tokensAsBigUnit(args.capacity);
|
|
29
|
+
|
|
30
|
+
const action = `${change} capacity ${size} channel ${args.channel}`;
|
|
31
|
+
const by = !!delta ? ` by ${tokensAsBigUnit(delta)}` : '';
|
|
32
|
+
const move = !!args.to_id ? ` and move channel to ${newPeer}` : '';
|
|
33
|
+
const type = hasType ? describeType(args.type) : '';
|
|
34
|
+
|
|
35
|
+
const changeType = hasType ? ` and make channel ${type}` : '';
|
|
36
|
+
|
|
37
|
+
return `${action} with ${peer}${by}${move}${changeType}?`;
|
|
38
|
+
};
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
const {encodeBigSize} = require('bolt01');
|
|
2
|
+
const {encodeTlvRecord} = require('bolt01');
|
|
2
3
|
const {rawChanId} = require('bolt07');
|
|
3
4
|
|
|
4
5
|
const channelFlagsType = '5';
|
|
5
6
|
const channelIdRecordType = '2';
|
|
7
|
+
const currentVersion = '1';
|
|
6
8
|
const decreaseRecordType = '3';
|
|
7
9
|
const increaseRecordType = '4';
|
|
10
|
+
const migrateRecordType = '6';
|
|
8
11
|
const requestIdRecordType = '1';
|
|
9
12
|
const typeAsHex = type => Buffer.from([type]).toString('hex');
|
|
13
|
+
const versionRecordType = '0';
|
|
10
14
|
|
|
11
15
|
/** Encode a request to change a channel capacity
|
|
12
16
|
|
|
@@ -15,6 +19,7 @@ const typeAsHex = type => Buffer.from([type]).toString('hex');
|
|
|
15
19
|
[decrease]: <Remove Channel Funds By Tokens Number>
|
|
16
20
|
id: <Request Id Hex String>
|
|
17
21
|
increase: <Add Channel Funds By Tokens Number>
|
|
22
|
+
[to]: <Move Channel to Different Node with Public Key Hex String>
|
|
18
23
|
type: <New Channel Type Number>
|
|
19
24
|
}
|
|
20
25
|
|
|
@@ -26,8 +31,12 @@ const typeAsHex = type => Buffer.from([type]).toString('hex');
|
|
|
26
31
|
}]
|
|
27
32
|
}
|
|
28
33
|
*/
|
|
29
|
-
module.exports = ({channel, decrease, id, increase, type}) => {
|
|
34
|
+
module.exports = ({channel, decrease, id, increase, to, type}) => {
|
|
30
35
|
const records = [
|
|
36
|
+
{
|
|
37
|
+
type: versionRecordType,
|
|
38
|
+
value: encodeBigSize({number: currentVersion}).encoded,
|
|
39
|
+
},
|
|
31
40
|
{
|
|
32
41
|
type: requestIdRecordType,
|
|
33
42
|
value: id,
|
|
@@ -42,21 +51,26 @@ module.exports = ({channel, decrease, id, increase, type}) => {
|
|
|
42
51
|
},
|
|
43
52
|
];
|
|
44
53
|
|
|
54
|
+
// Add a record reflecting the decrease in the capacity
|
|
45
55
|
if (!!decrease) {
|
|
46
|
-
// Add a record reflecting the decrease in the capacity
|
|
47
56
|
records.push({
|
|
48
57
|
type: decreaseRecordType,
|
|
49
58
|
value: encodeBigSize({number: decrease.toString()}).encoded,
|
|
50
59
|
});
|
|
51
60
|
}
|
|
52
61
|
|
|
62
|
+
// Add a record reflecting the increase in the capacity
|
|
53
63
|
if (!!increase) {
|
|
54
|
-
// Add a record reflecting the increase in the capacity
|
|
55
64
|
records.push({
|
|
56
65
|
type: increaseRecordType,
|
|
57
66
|
value: encodeBigSize({number: increase.toString()}).encoded,
|
|
58
67
|
});
|
|
59
68
|
}
|
|
60
69
|
|
|
70
|
+
// Add a record reflecting the migration of channel to a different peer
|
|
71
|
+
if (!!to) {
|
|
72
|
+
records.push({type: migrateRecordType, value: to});
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
return {records};
|
|
62
76
|
};
|
|
@@ -28,6 +28,7 @@ const waitForRequestsTimeoutMs = 5000;
|
|
|
28
28
|
from: <From Node Public Key Id Hex String>
|
|
29
29
|
id: <Change Request Id Hex String>
|
|
30
30
|
[increase]: <Add Capacity Tokens Number>
|
|
31
|
+
[to]: <Switch Channel to Node with Identity Public Key Hex String>
|
|
31
32
|
[type]: <Intended Replacement Channel Channel Type Flags Number>
|
|
32
33
|
}]
|
|
33
34
|
}
|
|
@@ -49,6 +49,7 @@ const txIdAsHash = id => Buffer.from(id, 'hex').reverse();
|
|
|
49
49
|
[increase]: <Increase Channel Size Tokens Number>
|
|
50
50
|
[is_private]: <Original Channel Is Private Bool>
|
|
51
51
|
lnd: <Authenticated LND API Object>
|
|
52
|
+
open_lnd: <Open Channel with LND API Object>
|
|
52
53
|
transaction_id: <Original Funding Transaction Id Hex String>
|
|
53
54
|
transaction_vout: <Original Funding Output Index Number>
|
|
54
55
|
}
|
|
@@ -87,6 +88,10 @@ module.exports = (args, cbk) => {
|
|
|
87
88
|
return cbk([400, 'ExpectedLndApiToGetCapacityReplacement']);
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
if (!args.open_lnd) {
|
|
92
|
+
return cbk([400, 'ExpectedLndToUseToOpenReplacementChannel']);
|
|
93
|
+
}
|
|
94
|
+
|
|
90
95
|
if (!args.transaction_id) {
|
|
91
96
|
return cbk([400, 'ExpectedFundingTxIdToGetCapacityReplacement']);
|
|
92
97
|
}
|
|
@@ -302,7 +307,7 @@ module.exports = (args, cbk) => {
|
|
|
302
307
|
partner_public_key: pendingChannel.partner_public_key,
|
|
303
308
|
}],
|
|
304
309
|
is_avoiding_broadcast: true,
|
|
305
|
-
lnd: args.
|
|
310
|
+
lnd: args.open_lnd,
|
|
306
311
|
},
|
|
307
312
|
cbk);
|
|
308
313
|
}],
|
|
@@ -24,6 +24,7 @@ const txIdAsHash = id => Buffer.from(id, 'hex').reverse();
|
|
|
24
24
|
id: <Channel Funding Transaction Id Hex String>
|
|
25
25
|
lnd: <Authenticated LND API Object>
|
|
26
26
|
output: <Original Funding Output Script Hex String>
|
|
27
|
+
to: <Replacement Channel with Peer Public Key Hex String>
|
|
27
28
|
unsigned: <Unsigned Replacement Transaction Hex String>
|
|
28
29
|
vout: <Channel Funding Output Index Number>
|
|
29
30
|
}
|
|
@@ -35,7 +36,7 @@ const txIdAsHash = id => Buffer.from(id, 'hex').reverse();
|
|
|
35
36
|
transaction_vout: <Replacement Transaction Output Index Number>
|
|
36
37
|
}
|
|
37
38
|
*/
|
|
38
|
-
module.exports = ({channel, id, lnd, output, unsigned, vout}, cbk) => {
|
|
39
|
+
module.exports = ({channel, id, lnd, output, to, unsigned, vout}, cbk) => {
|
|
39
40
|
return new Promise((resolve, reject) => {
|
|
40
41
|
return asyncAuto({
|
|
41
42
|
// Check arguments
|
|
@@ -56,6 +57,10 @@ module.exports = ({channel, id, lnd, output, unsigned, vout}, cbk) => {
|
|
|
56
57
|
return cbk([400, 'ExpectedOutputScriptToGetReplacementSignature']);
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
if (!to) {
|
|
61
|
+
return cbk([400, 'ExpectedChannelToPublicKeyHexString']);
|
|
62
|
+
}
|
|
63
|
+
|
|
59
64
|
if (!unsigned) {
|
|
60
65
|
return cbk([400, 'ExpectedUnsignedTxToGetReplacementSignature']);
|
|
61
66
|
}
|
|
@@ -164,8 +169,8 @@ module.exports = ({channel, id, lnd, output, unsigned, vout}, cbk) => {
|
|
|
164
169
|
return false;
|
|
165
170
|
}
|
|
166
171
|
|
|
167
|
-
// The new channel must be from the
|
|
168
|
-
if (channel.partner_public_key !==
|
|
172
|
+
// The new channel must be from the expected node
|
|
173
|
+
if (channel.partner_public_key !== to) {
|
|
169
174
|
return false;
|
|
170
175
|
}
|
|
171
176
|
|
|
@@ -36,6 +36,7 @@ const familyTemporary = 805;
|
|
|
36
36
|
const {fromHex} = Transaction;
|
|
37
37
|
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
38
38
|
const idRecordType = '0';
|
|
39
|
+
const {isArray} = Array;
|
|
39
40
|
const makeId = () => randomBytes(32).toString('hex');
|
|
40
41
|
const messageFrequencyMs = 3000;
|
|
41
42
|
const minimumAddTokens = 20000;
|
|
@@ -59,6 +60,11 @@ const waitingTimeoutMs = 1000 * 30;
|
|
|
59
60
|
ask: <Ask Function>
|
|
60
61
|
lnd: <Authenticated LND API Object>
|
|
61
62
|
logger: <Winston Logger Object>
|
|
63
|
+
nodes: [{
|
|
64
|
+
lnd: <Potential Move Node LND API Object>
|
|
65
|
+
node_name: <Potential Move Node Name String>
|
|
66
|
+
public_key: <Potential Move Node Public Key Hex String>
|
|
67
|
+
}]
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
@returns via cbk or Promise
|
|
@@ -66,11 +72,12 @@ const waitingTimeoutMs = 1000 * 30;
|
|
|
66
72
|
base_fee_mtokens: <Forwarding Base Fee Millitokens String>
|
|
67
73
|
cltv_delta: <Forwarding CLTV Delta Number>
|
|
68
74
|
fee_rate: <Forwarding Parts Per Million Fee Rate Number>
|
|
75
|
+
open_lnd: <Channel Open LND API Object>
|
|
69
76
|
transaction_id: <Replacement Channel Transaction Id Hex String>
|
|
70
77
|
transaction_vout: <Replacement Channel Transaction Output Index Number>
|
|
71
78
|
}
|
|
72
79
|
*/
|
|
73
|
-
module.exports = ({ask, lnd, logger}, cbk) => {
|
|
80
|
+
module.exports = ({ask, lnd, logger, nodes}, cbk) => {
|
|
74
81
|
return new Promise((resolve, reject) => {
|
|
75
82
|
return asyncAuto({
|
|
76
83
|
// Generate a unique identifier for the change
|
|
@@ -90,6 +97,10 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
90
97
|
return cbk([400, 'ExpectedLoggerObjectToInitiateCapacityChange']);
|
|
91
98
|
}
|
|
92
99
|
|
|
100
|
+
if (!isArray(nodes)) {
|
|
101
|
+
return cbk([400, 'ExpectedArrayOfCandidateMoveNodesToInitiate']);
|
|
102
|
+
}
|
|
103
|
+
|
|
93
104
|
return cbk();
|
|
94
105
|
},
|
|
95
106
|
|
|
@@ -98,18 +109,18 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
98
109
|
|
|
99
110
|
// Ask for all the capacity change details
|
|
100
111
|
askForChangeDetails: ['id', 'validate', ({id}, cbk) => {
|
|
101
|
-
return askForChangeDetails({ask, id, lnd}, cbk);
|
|
112
|
+
return askForChangeDetails({ask, id, lnd, nodes}, cbk);
|
|
102
113
|
}],
|
|
103
114
|
|
|
104
115
|
// Propose the theoretical new channel to the peer to confirm a change
|
|
105
116
|
checkAccept: ['askForChangeDetails', ({askForChangeDetails}, cbk) => {
|
|
106
117
|
// Check that a channel open would be accepted
|
|
107
118
|
return acceptsChannelOpen({
|
|
108
|
-
lnd,
|
|
109
119
|
capacity: askForChangeDetails.estimated_capacity,
|
|
110
120
|
cooperative_close_address: askForChangeDetails.coop_close_address,
|
|
111
121
|
give_tokens: askForChangeDetails.remote_balance,
|
|
112
122
|
is_private: askForChangeDetails.is_private,
|
|
123
|
+
lnd: askForChangeDetails.open_lnd,
|
|
113
124
|
min_htlc_mtokens: askForChangeDetails.min_htlc_mtokens,
|
|
114
125
|
partner_csv_delay: askForChangeDetails.partner_csv_delay,
|
|
115
126
|
partner_public_key: askForChangeDetails.partner_public_key,
|
|
@@ -403,6 +414,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
403
414
|
asyncReflect(({
|
|
404
415
|
askForChangeDetails,
|
|
405
416
|
getFunding,
|
|
417
|
+
getMigrationLnd,
|
|
406
418
|
getNetwork,
|
|
407
419
|
getPeerChannels,
|
|
408
420
|
id,
|
|
@@ -438,6 +450,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
438
450
|
increase_transaction_vout: getFunding.vout,
|
|
439
451
|
increase_witness_script: getFunding.script,
|
|
440
452
|
is_private: askForChangeDetails.is_private,
|
|
453
|
+
open_lnd: askForChangeDetails.open_lnd,
|
|
441
454
|
open_transaction: sendBasicRequest,
|
|
442
455
|
partner_public_key: askForChangeDetails.partner_public_key,
|
|
443
456
|
transaction_id: channel.transaction_id,
|
|
@@ -463,6 +476,8 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
463
476
|
base_fee_mtokens: askForChangeDetails.base_fee_mtokens,
|
|
464
477
|
cltv_delta: askForChangeDetails.cltv_delta,
|
|
465
478
|
fee_rate: askForChangeDetails.fee_rate,
|
|
479
|
+
open_from: askForChangeDetails.open_from || undefined,
|
|
480
|
+
open_lnd: askForChangeDetails.open_lnd,
|
|
466
481
|
transaction_id: proposeChange.value.transaction_id,
|
|
467
482
|
transaction_vout: proposeChange.value.transaction_vout,
|
|
468
483
|
});
|
|
@@ -4,16 +4,18 @@ const {decodeBigSize} = require('bolt01');
|
|
|
4
4
|
const channelIdHexLength = 16;
|
|
5
5
|
const decodeNumber = encoded => BigInt(decodeBigSize({encoded}).decoded);
|
|
6
6
|
const defaultRecord = {value: '00'};
|
|
7
|
-
const hexAsNumber = n => Buffer.from(n, 'hex').readUInt8();
|
|
8
|
-
const idHexLength = 64;
|
|
9
|
-
const tooLarge = BigInt(Number.MAX_SAFE_INTEGER);
|
|
10
|
-
|
|
11
7
|
const findChannelRecord = records => records.find(n => n.type === '2');
|
|
12
8
|
const findDecreaseRecord = records => records.find(n => n.type === '3');
|
|
13
9
|
const findIncreaseRecord = records => records.find(n => n.type === '4');
|
|
10
|
+
const findMigrationRecord = records => records.find(n => n.type === '6');
|
|
14
11
|
const findRequestIdRecord = records => records.find(n => n.type === '1');
|
|
15
12
|
const findTypeRecord = records => records.find(n => n.type === '5');
|
|
16
13
|
const findVersionRecord = records => records.find(n => n.type === '0');
|
|
14
|
+
const hexAsNumber = n => Buffer.from(n, 'hex').readUInt8();
|
|
15
|
+
const idHexLength = 64;
|
|
16
|
+
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
17
|
+
const knownVersions = ['01'];
|
|
18
|
+
const tooLarge = BigInt(Number.MAX_SAFE_INTEGER);
|
|
17
19
|
|
|
18
20
|
/** Parse a capacity change request
|
|
19
21
|
|
|
@@ -34,6 +36,7 @@ const findVersionRecord = records => records.find(n => n.type === '0');
|
|
|
34
36
|
id: <Change Request Id Hex String>
|
|
35
37
|
[increase]: <Add Capacity Tokens Number>
|
|
36
38
|
[type]: <Intended Replacement Channel Channel Type Flags Number>
|
|
39
|
+
[to]: <Replace Channel with New Peer with Identity Public Key Hex String>
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
*/
|
|
@@ -43,8 +46,10 @@ module.exports = ({from, records}) => {
|
|
|
43
46
|
return {};
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
const versionRecord = findVersionRecord(records);
|
|
50
|
+
|
|
51
|
+
// Exit early when the request is an unknown version
|
|
52
|
+
if (!!versionRecord && !knownVersions.includes(versionRecord.value)) {
|
|
48
53
|
return {};
|
|
49
54
|
}
|
|
50
55
|
|
|
@@ -71,6 +76,7 @@ module.exports = ({from, records}) => {
|
|
|
71
76
|
|
|
72
77
|
const decreaseRecord = findDecreaseRecord(records);
|
|
73
78
|
const increaseRecord = findIncreaseRecord(records);
|
|
79
|
+
const migrationRecord = findMigrationRecord(records);
|
|
74
80
|
const typeRecord = findTypeRecord(records);
|
|
75
81
|
|
|
76
82
|
// Exit early when there is a change in both directions
|
|
@@ -82,8 +88,8 @@ module.exports = ({from, records}) => {
|
|
|
82
88
|
const id = idRecord.value;
|
|
83
89
|
const type = typeRecord ? hexAsNumber(typeRecord.value) : undefined;
|
|
84
90
|
|
|
85
|
-
// Exit early when there is no decrease or
|
|
86
|
-
if (!decreaseRecord && !increaseRecord) {
|
|
91
|
+
// Exit early when there is no decrease, increase or migration record
|
|
92
|
+
if (!decreaseRecord && !increaseRecord && !migrationRecord) {
|
|
87
93
|
return {request: {channel, from, id, type}};
|
|
88
94
|
}
|
|
89
95
|
|
|
@@ -95,6 +101,21 @@ module.exports = ({from, records}) => {
|
|
|
95
101
|
return {};
|
|
96
102
|
}
|
|
97
103
|
|
|
104
|
+
// Exit early when a migration record is present on a too-old version
|
|
105
|
+
if (!!migrationRecord && !versionRecord) {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Exit early when a migration record is not a public key
|
|
110
|
+
if (!!migrationRecord && !isPublicKey(migrationRecord.value)) {
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Exit early when a migration record has the same key as the peer
|
|
115
|
+
if (!!migrationRecord && migrationRecord.value === from) {
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
|
|
98
119
|
return {
|
|
99
120
|
request: {
|
|
100
121
|
channel,
|
|
@@ -103,6 +124,7 @@ module.exports = ({from, records}) => {
|
|
|
103
124
|
type,
|
|
104
125
|
decrease: Number(decrease) || undefined,
|
|
105
126
|
increase: Number(increase) || undefined,
|
|
127
|
+
to: !!migrationRecord ? migrationRecord.value : undefined,
|
|
106
128
|
},
|
|
107
129
|
};
|
|
108
130
|
};
|
|
@@ -53,6 +53,7 @@ const unsignedTransactionType = '1';
|
|
|
53
53
|
[increase_witness_script]: <Increase Funds Witness Script Hex String>
|
|
54
54
|
lnd: <Authenticated LND API Object>
|
|
55
55
|
logger: <Winston Logger Object>
|
|
56
|
+
open_lnd: <Open Replacement Channel Using LND API Object>
|
|
56
57
|
open_transaction: <Original Channel Funding Transaction Hex String>
|
|
57
58
|
partner_public_key: <Peer Public Key Hex Encoded String>
|
|
58
59
|
transaction_id: <Original Channel Transaction Id Hex String>
|
|
@@ -94,6 +95,10 @@ module.exports = (args, cbk) => {
|
|
|
94
95
|
return cbk([400, 'ExpectedWinstonLoggerToProposeCapacityChange']);
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
if (!args.open_lnd) {
|
|
99
|
+
return cbk([400, 'ExpectedOpenLndApiToProposeCapacityChange']);
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
if (!args.open_transaction) {
|
|
98
103
|
return cbk([400, 'ExpectedChannelOpenTxToProposeCapacityChange']);
|
|
99
104
|
}
|
|
@@ -125,6 +130,7 @@ module.exports = (args, cbk) => {
|
|
|
125
130
|
increase: args.increase,
|
|
126
131
|
is_private: args.is_private,
|
|
127
132
|
lnd: args.lnd,
|
|
133
|
+
open_lnd: args.open_lnd,
|
|
128
134
|
open_transaction: args.open_transaction,
|
|
129
135
|
transaction_id: args.transaction_id,
|
|
130
136
|
transaction_vout: args.transaction_vout,
|
|
@@ -200,7 +206,7 @@ module.exports = (args, cbk) => {
|
|
|
200
206
|
return fundPendingChannels({
|
|
201
207
|
funding,
|
|
202
208
|
channels: [getReplacement.pending_channel_id],
|
|
203
|
-
lnd: args.
|
|
209
|
+
lnd: args.open_lnd,
|
|
204
210
|
},
|
|
205
211
|
cbk);
|
|
206
212
|
}],
|
|
@@ -361,7 +367,7 @@ module.exports = (args, cbk) => {
|
|
|
361
367
|
|
|
362
368
|
// Look for the new channel to see if the change succeeded
|
|
363
369
|
try {
|
|
364
|
-
const {channels} = await getChannels({lnd: args.
|
|
370
|
+
const {channels} = await getChannels({lnd: args.open_lnd});
|
|
365
371
|
|
|
366
372
|
const channel = channels.find(n => n.transaction_id === txId);
|
|
367
373
|
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"bolt01": "1.2.3",
|
|
13
13
|
"bolt07": "1.8.0",
|
|
14
14
|
"invoices": "2.0.3",
|
|
15
|
-
"ln-service": "53.5.
|
|
15
|
+
"ln-service": "53.5.1",
|
|
16
16
|
"ln-sync": "3.7.0"
|
|
17
17
|
},
|
|
18
18
|
"description": "Lightning Paid Services library",
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"integration-tests": "tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 180 test/integration/*.js",
|
|
41
41
|
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/actions/*.js test/capacity/*.js test/client/*.js test/config/*.js test/p2p/*.js test/records/*.js test/respond/*.js test/server/*.js test/server/*.js test/services/*.js test/trades/*.js"
|
|
42
42
|
},
|
|
43
|
-
"version": "3.
|
|
43
|
+
"version": "3.11.0"
|
|
44
44
|
}
|
|
@@ -105,6 +105,7 @@ test(`Accept capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
105
105
|
bitcoinjs_network: (await getNetwork({lnd})).bitcoinjs,
|
|
106
106
|
channel: channel.id,
|
|
107
107
|
decrease: [],
|
|
108
|
+
open_lnd: lnd,
|
|
108
109
|
open_transaction: tx.transaction,
|
|
109
110
|
partner_public_key: target.id,
|
|
110
111
|
transaction_id: channel.transaction_id,
|
|
@@ -123,6 +124,7 @@ test(`Accept capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
123
124
|
channel: channel.id,
|
|
124
125
|
from: control.id,
|
|
125
126
|
lnd: target.lnd,
|
|
127
|
+
to: control.id,
|
|
126
128
|
});
|
|
127
129
|
},
|
|
128
130
|
|
|
@@ -114,6 +114,7 @@ test(`Accept capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
114
114
|
throw new Error('UnknownQueryNameForInitiator');
|
|
115
115
|
},
|
|
116
116
|
logger: {error: log, info: log},
|
|
117
|
+
nodes: [],
|
|
117
118
|
});
|
|
118
119
|
},
|
|
119
120
|
|
|
@@ -146,6 +147,7 @@ test(`Accept capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
146
147
|
},
|
|
147
148
|
lnd: target.lnd,
|
|
148
149
|
logger: {error: log, info: log},
|
|
150
|
+
nodes: [],
|
|
149
151
|
});
|
|
150
152
|
}],
|
|
151
153
|
|
|
@@ -127,6 +127,7 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
127
127
|
|
|
128
128
|
throw new Error('UnexpectedQueryNameForProposingSide');
|
|
129
129
|
},
|
|
130
|
+
nodes: [],
|
|
130
131
|
});
|
|
131
132
|
},
|
|
132
133
|
|
|
@@ -159,6 +160,7 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
159
160
|
throw new Error('UnexpectedQueryNameForAcceptingSide');
|
|
160
161
|
},
|
|
161
162
|
lnd: target.lnd,
|
|
163
|
+
nodes: [],
|
|
162
164
|
});
|
|
163
165
|
}],
|
|
164
166
|
|
|
@@ -116,6 +116,7 @@ test(`Accept capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
116
116
|
|
|
117
117
|
throw new Error('UnexpectedQueryNameForProposingSide');
|
|
118
118
|
},
|
|
119
|
+
nodes: [],
|
|
119
120
|
});
|
|
120
121
|
},
|
|
121
122
|
|
|
@@ -146,6 +147,7 @@ test(`Accept capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
146
147
|
throw new Error('UnexpectedQueryNameForAcceptingSide');
|
|
147
148
|
},
|
|
148
149
|
lnd: target.lnd,
|
|
150
|
+
nodes: [],
|
|
149
151
|
});
|
|
150
152
|
}],
|
|
151
153
|
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
const {addPeer} = require('ln-service');
|
|
2
|
+
const {address} = require('bitcoinjs-lib');
|
|
3
|
+
const asyncAuto = require('async/auto');
|
|
4
|
+
const asyncMap = require('async/map');
|
|
5
|
+
const asyncRetry = require('async/retry');
|
|
6
|
+
const {broadcastChainTransaction} = require('ln-service');
|
|
7
|
+
const {closeChannel} = require('ln-service');
|
|
8
|
+
const {createChainAddress} = require('ln-service');
|
|
9
|
+
const {getChainTransactions} = require('ln-service');
|
|
10
|
+
const {getChannel} = require('ln-service');
|
|
11
|
+
const {getChannels} = require('ln-service');
|
|
12
|
+
const {getNetwork} = require('ln-sync');
|
|
13
|
+
const {networks} = require('bitcoinjs-lib');
|
|
14
|
+
const {openChannel} = require('ln-service');
|
|
15
|
+
const {spawnLightningCluster} = require('ln-docker-daemons');
|
|
16
|
+
const {test} = require('@alexbosworth/tap');
|
|
17
|
+
const {Transaction} = require('bitcoinjs-lib');
|
|
18
|
+
|
|
19
|
+
const {changeChannelCapacity} = require('./../../capacity');
|
|
20
|
+
const {getCapacityChangeRequests} = require('./../../capacity');
|
|
21
|
+
|
|
22
|
+
const bufferAsHex = buffer => buffer.toString('hex');
|
|
23
|
+
const capacity = 1e6;
|
|
24
|
+
const {fromHex} = Transaction;
|
|
25
|
+
const id = Buffer.alloc(32).toString('hex');
|
|
26
|
+
const interval = 10;
|
|
27
|
+
const log = () => {};
|
|
28
|
+
const maturity = 100;
|
|
29
|
+
const size = 3;
|
|
30
|
+
const slow = 5000;
|
|
31
|
+
const times = 2000;
|
|
32
|
+
const {toOutputScript} = address;
|
|
33
|
+
const weightAsVBytes = n => Math.ceil(n / 4);
|
|
34
|
+
|
|
35
|
+
// A capacity movement proposal should be counter signed and accepted
|
|
36
|
+
test(`Move channel capacity`, async ({end, equal, strictSame}) => {
|
|
37
|
+
const {kill, nodes} = await spawnLightningCluster({size});
|
|
38
|
+
|
|
39
|
+
const [control, target, remote] = nodes;
|
|
40
|
+
|
|
41
|
+
const {generate, lnd} = control;
|
|
42
|
+
|
|
43
|
+
const network = networks[(await getNetwork({lnd})).bitcoinjs];
|
|
44
|
+
|
|
45
|
+
// Make some funds
|
|
46
|
+
await generate({count: maturity});
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Connect remote and target
|
|
50
|
+
await addPeer({
|
|
51
|
+
lnd: target.lnd,
|
|
52
|
+
public_key: remote.id,
|
|
53
|
+
socket: remote.socket,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Open up a new channel
|
|
57
|
+
const channelOpen = await openChannel({
|
|
58
|
+
lnd,
|
|
59
|
+
is_private: true,
|
|
60
|
+
local_tokens: capacity,
|
|
61
|
+
partner_public_key: target.id,
|
|
62
|
+
partner_socket: target.socket,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Wait for the channel to be active
|
|
66
|
+
const channel = await asyncRetry({interval, times}, async () => {
|
|
67
|
+
const [channel] = (await getChannels({lnd, is_active: true})).channels;
|
|
68
|
+
|
|
69
|
+
if (!channel) {
|
|
70
|
+
await generate({});
|
|
71
|
+
|
|
72
|
+
throw new Error('ExpectedChannelActivation');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const {policies} = await getChannel({lnd, id: channel.id});
|
|
76
|
+
|
|
77
|
+
const noPolicy = policies.find(n => !n.cltv_delta);
|
|
78
|
+
|
|
79
|
+
if (!!channel && !noPolicy) {
|
|
80
|
+
return channel;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await generate({});
|
|
84
|
+
|
|
85
|
+
throw new Error('ExpectedChannelActivation');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await asyncAuto({
|
|
89
|
+
// Propose the channel
|
|
90
|
+
propose: async () => {
|
|
91
|
+
await changeChannelCapacity({
|
|
92
|
+
lnd,
|
|
93
|
+
ask: (args, cbk) => {
|
|
94
|
+
if (args.name === 'amount') {
|
|
95
|
+
return cbk({amount: '0'});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (args.name === 'decrease') {
|
|
99
|
+
return cbk({decrease: 'internal_spend_funds'});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (args.name === 'direction') {
|
|
103
|
+
return cbk({direction: 'migrate'});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (args.name === 'ok') {
|
|
107
|
+
return cbk({ok: true});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (args.name === 'migration') {
|
|
111
|
+
return cbk({migration: 0});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (args.name === 'proceed') {
|
|
115
|
+
return cbk({proceed: true});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (args.name === 'query') {
|
|
119
|
+
return cbk({query: target.id});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (args.name === 'type') {
|
|
123
|
+
return cbk({type: args.default});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
throw new Error('UnknownQueryNameForInitiator');
|
|
127
|
+
},
|
|
128
|
+
logger: {error: log, info: log},
|
|
129
|
+
nodes: [{
|
|
130
|
+
from: 'from',
|
|
131
|
+
lnd: remote.lnd,
|
|
132
|
+
public_key: remote.id,
|
|
133
|
+
}],
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
// Wait to see the inbound proposal
|
|
138
|
+
waitForProposal: async () => {
|
|
139
|
+
await asyncRetry({interval, times}, async () => {
|
|
140
|
+
const {requests} = await getCapacityChangeRequests({
|
|
141
|
+
lnd: target.lnd,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!requests.length) {
|
|
145
|
+
throw new Error('FailedToFindChangeRequest');
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
// Accept the channel
|
|
151
|
+
accept: ['waitForProposal', async ({}) => {
|
|
152
|
+
await changeChannelCapacity({
|
|
153
|
+
ask: (args, cbk) => {
|
|
154
|
+
if (args.name === 'accept') {
|
|
155
|
+
return cbk({accept: true});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (args.name === 'ok') {
|
|
159
|
+
return cbk({ok: true});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
throw new Error('UnexpectedQueryForAcceptingPeer');
|
|
163
|
+
},
|
|
164
|
+
lnd: target.lnd,
|
|
165
|
+
logger: {error: log, info: log},
|
|
166
|
+
nodes: [],
|
|
167
|
+
});
|
|
168
|
+
}],
|
|
169
|
+
|
|
170
|
+
// Generate to confirm the change
|
|
171
|
+
generate: ['waitForProposal', async ({}) => {
|
|
172
|
+
// Wait for the new channel to be active
|
|
173
|
+
const recreated = await asyncRetry({
|
|
174
|
+
times,
|
|
175
|
+
interval: slow,
|
|
176
|
+
},
|
|
177
|
+
async () => {
|
|
178
|
+
const {channels} = await getChannels({
|
|
179
|
+
lnd: target.lnd,
|
|
180
|
+
is_active: true,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const [channel] = channels.filter(n => n.capacity < capacity);
|
|
184
|
+
|
|
185
|
+
await generate({});
|
|
186
|
+
|
|
187
|
+
if (!channel) {
|
|
188
|
+
throw new Error('ExpectedChannelActivation');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
equal(channel.partner_public_key, remote.id, 'Channel is moved');
|
|
192
|
+
|
|
193
|
+
{
|
|
194
|
+
const {policies} = await getChannel({
|
|
195
|
+
lnd: remote.lnd,
|
|
196
|
+
id: channel.id,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const noPolicy = policies.find(n => !n.cltv_delta);
|
|
200
|
+
|
|
201
|
+
if (!!noPolicy) {
|
|
202
|
+
throw new Error('ExpectedChannelPolicy');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
{
|
|
207
|
+
const {policies} = await getChannel({
|
|
208
|
+
id: channel.id,
|
|
209
|
+
lnd: target.lnd,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const noPolicy = policies.find(n => !n.cltv_delta);
|
|
213
|
+
|
|
214
|
+
if (!!noPolicy) {
|
|
215
|
+
throw new Error('ExpectedChannelPolicy');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return;
|
|
220
|
+
});
|
|
221
|
+
}],
|
|
222
|
+
});
|
|
223
|
+
} catch (err) {
|
|
224
|
+
strictSame(err, null, 'Expected no failure');
|
|
225
|
+
} finally {
|
|
226
|
+
await kill({});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return end();
|
|
230
|
+
});
|
|
@@ -141,6 +141,7 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
141
141
|
|
|
142
142
|
throw new Error('UnexpectedQueryNameForProposingSide');
|
|
143
143
|
},
|
|
144
|
+
nodes: [],
|
|
144
145
|
});
|
|
145
146
|
},
|
|
146
147
|
|
|
@@ -173,6 +174,7 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
173
174
|
throw new Error('UnexpectedQueryNameForAcceptingSide');
|
|
174
175
|
},
|
|
175
176
|
lnd: target.lnd,
|
|
177
|
+
nodes: [],
|
|
176
178
|
});
|
|
177
179
|
}],
|
|
178
180
|
|