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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Versions
2
2
 
3
+ ## Version 3.11.0
4
+
5
+ - `changeChannelCapacity`: Add `nodes` to allow moving channel to another node
6
+
3
7
  ## Version 3.10.0
4
8
 
5
9
  - `createAnchoredTrade`: Add method to create an anchored open trade
@@ -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 capacity?',
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: `${pay} for capacity change?`,
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 describeType = type => !(type & 0) ? 'private' : 'public';
14
- const interval = 10 * 1000;
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 getNodeAlias({lnd, id: request.from}, (err, res) => {
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
- const change = !!request.increase ? 'Increase' : 'Decrease';
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: ['askToSelectChange', ({askToSelectChange}, cbk) => {
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({ask, lnd, logger}, cbk);
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.lnd,
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 same peer
168
- if (channel.partner_public_key !== pending.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
- // Exit early when there is a version record, indicating a future version
47
- if (!!findVersionRecord(records)) {
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 increase
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.lnd,
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.lnd});
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.0",
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.10.0"
43
+ "version": "3.11.0"
44
44
  }
@@ -34,6 +34,7 @@ const tests = [
34
34
  from: Buffer.alloc(33, 3).toString('hex'),
35
35
  id: Buffer.alloc(32).toString('hex'),
36
36
  increase: undefined,
37
+ to: undefined,
37
38
  type: undefined,
38
39
  }],
39
40
  },
@@ -22,6 +22,10 @@ const tests = [
22
22
  description: 'Encode change request parameters',
23
23
  expected: {
24
24
  records: [
25
+ {
26
+ type: '0',
27
+ value: '01',
28
+ },
25
29
  {
26
30
  type: '1',
27
31
  value: '0000000000000000000000000000000000000000000000000000000000000000',
@@ -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