paid-services 3.4.0 → 3.7.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.
Files changed (38) hide show
  1. package/.prettierignore +2 -0
  2. package/CHANGELOG.md +16 -0
  3. package/capacity/accept_capacity_change.js +2 -0
  4. package/capacity/ask_for_change_details.js +41 -9
  5. package/capacity/ask_for_decrease.js +167 -49
  6. package/capacity/capacity_change_requests.js +9 -0
  7. package/capacity/change_channel_capacity.js +7 -1
  8. package/capacity/encode_change_request.js +8 -1
  9. package/capacity/fee_for_replacement.js +3 -7
  10. package/capacity/get_capacity_change_requests.js +1 -0
  11. package/capacity/get_capacity_replacement.js +56 -7
  12. package/capacity/initiate_capacity_change.js +1 -2
  13. package/capacity/parse_capacity_change_request.js +8 -1
  14. package/capacity/propose_capacity_change.js +208 -92
  15. package/capacity/propose_channel_open.js +54 -0
  16. package/package.json +9 -9
  17. package/services/response_for_relay.js +0 -1
  18. package/test/capacity/test_capacity_change_requests.js +1 -0
  19. package/test/capacity/test_encode_change_request.js +5 -0
  20. package/test/capacity/test_parse_capacity_change_request.js +1 -0
  21. package/test/integration/test_accept_capacity_change.js +3 -4
  22. package/test/integration/test_change_channel_capacity.js +8 -1
  23. package/test/integration/test_decrease_channel_capacity.js +23 -8
  24. package/test/integration/test_increase_channel_capacity.js +4 -1
  25. package/test/integration/test_split_channel_capacity.js +241 -0
  26. package/trades/accept_trade.js +12 -3
  27. package/trades/create_trade.js +82 -50
  28. package/trades/decode_anchored_trade.js +56 -0
  29. package/trades/encode_anchored_trade.js +35 -0
  30. package/trades/finalize_trade_secret.js +7 -0
  31. package/trades/find_trade.js +28 -3
  32. package/trades/manage_trade.js +15 -4
  33. package/trades/manage_trades.js +209 -2
  34. package/trades/request_trade_by_id.js +7 -1
  35. package/trades/service_anchored_trades.js +181 -0
  36. package/trades/service_open_trade.js +172 -0
  37. package/trades/service_trade_requests.js +106 -35
  38. package/trades/trade_from_invoice.js +51 -0
@@ -1,10 +1,14 @@
1
+ const {address} = require('bitcoinjs-lib');
1
2
  const asyncAuto = require('async/auto');
3
+ const asyncEach = require('async/each');
2
4
  const {closeChannel} = require('ln-service');
5
+ const {connectPeer} = require('ln-sync');
3
6
  const {decodeChanId} = require('bolt07');
4
7
  const {getChainFeeRate} = require('ln-service');
5
8
  const {getChainTransactions} = require('ln-service');
6
9
  const {getHeight} = require('ln-service');
7
10
  const {getPendingChannels} = require('ln-service');
11
+ const {networks} = require('bitcoinjs-lib');
8
12
  const {openChannels} = require('ln-service');
9
13
  const {returnResult} = require('asyncjs-util');
10
14
  const {Transaction} = require('bitcoinjs-lib');
@@ -26,6 +30,7 @@ const notFoundIndex = -1;
26
30
  const positive = n => Math.max(1, n);
27
31
  const slowTarget = 1000;
28
32
  const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
33
+ const {toOutputScript} = address;
29
34
  const txIdAsHash = id => Buffer.from(id, 'hex').reverse();
30
35
 
31
36
  /** Close a channel and get a capacity replacement transaction
@@ -36,7 +41,8 @@ const txIdAsHash = id => Buffer.from(id, 'hex').reverse();
36
41
  bitcoinjs_network: <BitcoinJs Network Name String>
37
42
  decrease: [{
38
43
  address: <Spend to Chain Address String>
39
- output: <Spend to Output Script Hex String>
44
+ [node]: <Node to Open Channel to Public Key Hex String>
45
+ [output]: <Spend to Output Script Hex String>
40
46
  tokens: <Spend Value Number>
41
47
  }]
42
48
  id: <Original Channel Standard Format Channel Id String>
@@ -51,10 +57,12 @@ const txIdAsHash = id => Buffer.from(id, 'hex').reverse();
51
57
  {
52
58
  [add_funds_vin]: <Funding Spend Input Index Number>
53
59
  capacity: <Replacement Channel Capacity Tokens Number>
60
+ force_close_tx: <Raw Force Close Transaction Hex String>
61
+ open_pending_ids: [<Proposed Additional Channel Id String>]
62
+ pending_channel_id: <Proposed Replacement Channel Pending Id Hex String>
54
63
  remote_balance: <Initial Balance for Peer Tokens Number>
55
64
  signature: <Signature for Replacement Transaction Hex Encoded String>
56
65
  signing_key: <Signing Public Key Hex String>
57
- pending_channel_id: <Proposed Channel Pending Id Hex String>
58
66
  transaction_id: <Replacement Channel Transaction Id Hex String>
59
67
  transaction_vin: <Original Channel Input Index Number>
60
68
  transaction_vout: <Replacement Channel Transaction Output Index Number>
@@ -102,8 +110,34 @@ module.exports = (args, cbk) => {
102
110
  // Get the current chain height
103
111
  getHeight: ['validate', ({}, cbk) => getHeight({lnd: args.lnd}, cbk)],
104
112
 
113
+ // Connect to nodes to open channels to
114
+ connect: ['validate', ({}, cbk) => {
115
+ return asyncEach(args.decrease.filter(n => !!n.node), (spend, cbk) => {
116
+ return connectPeer({id: spend.node, lnd: args.lnd}, cbk);
117
+ },
118
+ cbk);
119
+ }],
120
+
121
+ // Propose any additional channels to nodes
122
+ openChannels: ['connect', ({}, cbk) => {
123
+ // Exit early when there are no nodes to propose channels to
124
+ if (!args.decrease.filter(n => !!n.node).length) {
125
+ return cbk(null, {pending: []});
126
+ }
127
+
128
+ return openChannels({
129
+ channels: args.decrease.filter(n => !!n.node).map(decrease => ({
130
+ capacity: decrease.tokens,
131
+ partner_public_key: decrease.node,
132
+ })),
133
+ is_avoiding_broadcast: true,
134
+ lnd: args.lnd,
135
+ },
136
+ cbk);
137
+ }],
138
+
105
139
  // Force close the channel in order to get a non-final sequence number
106
- channelClose: ['getFeeRate', ({}, cbk) => {
140
+ channelClose: ['getFeeRate','openChannels', ({}, cbk) => {
107
141
  return closeChannel({
108
142
  lnd: args.lnd,
109
143
  is_force_close: true,
@@ -277,19 +311,31 @@ module.exports = (args, cbk) => {
277
311
  replacementTx: [
278
312
  'closeTx',
279
313
  'newCapacity',
314
+ 'openChannels',
280
315
  'proposeChannel',
281
- ({closeTx, newCapacity, proposeChannel}, cbk) =>
316
+ ({closeTx, newCapacity, openChannels, proposeChannel}, cbk) =>
282
317
  {
283
318
  // The replacement tx pays to the proposed channel multisig address
284
319
  const [{address}] = proposeChannel.pending;
285
320
 
321
+ const network = networks[args.bitcoinjs_network];
322
+
323
+ // Map pending channel opens to match the spends
324
+ const openAsSpends = openChannels.pending.map(({address, tokens}) => ({
325
+ tokens,
326
+ output: bufferAsHex(toOutputScript(address, network)),
327
+ }));
328
+
329
+ // Decreases are spends to regular addresses plus channel outputs
330
+ const decreases = [].concat(args.decrease).concat(openAsSpends);
331
+
286
332
  try {
287
333
  const replacement = assembleReplacementTx({
288
334
  add_funds_transaction_id: args.add_funds_transaction_id,
289
335
  add_funds_transaction_vout: args.add_funds_transaction_vout,
290
336
  bitcoinjs_network: args.bitcoinjs_network,
291
337
  close_transaction: closeTx.transaction,
292
- decrease: args.decrease.map(decrease => ({
338
+ decrease: decreases.filter(n => !!n.output).map(decrease => ({
293
339
  output: decrease.output,
294
340
  tokens: decrease.tokens,
295
341
  })),
@@ -334,6 +380,7 @@ module.exports = (args, cbk) => {
334
380
  proposedReplacement: [
335
381
  'closeTx',
336
382
  'newCapacity',
383
+ 'openChannels',
337
384
  'pendingChannel',
338
385
  'proposeChannel',
339
386
  'replacementTx',
@@ -341,6 +388,7 @@ module.exports = (args, cbk) => {
341
388
  ({
342
389
  closeTx,
343
390
  newCapacity,
391
+ openChannels,
344
392
  pendingChannel,
345
393
  proposeChannel,
346
394
  replacementTx,
@@ -353,11 +401,12 @@ module.exports = (args, cbk) => {
353
401
  return cbk(null, {
354
402
  add_funds_vin: replacementTx.add_funds_vin,
355
403
  capacity: newCapacity,
356
- force_close_tx_id: closeTx.id,
404
+ force_close_tx: closeTx.transaction,
405
+ open_pending_ids: openChannels.pending.map(n => n.id),
406
+ pending_channel_id: pending.id,
357
407
  remote_balance: pendingChannel.remote_balance,
358
408
  signature: signReplacement.signature,
359
409
  signing_key: signReplacement.key,
360
- pending_channel_id: pending.id,
361
410
  transaction_id: replacementTx.transaction_id,
362
411
  transaction_vin: replacementTx.transaction_vin,
363
412
  transaction_vout: replacementTx.transaction_vout,
@@ -47,7 +47,6 @@ const rebroadcastTimeMs = 1000 * 5;
47
47
  const recordsAsObject = a => a.reduce((sum, n) => sum[n.type] = n.value, {});
48
48
  const requestCapacityChangeIntervalMs = 2000;
49
49
  const requestCapacityChangeTimeoutMs = 1000 * 60 * 60 * 6;
50
- const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
51
50
  const testMessage = '00';
52
51
  const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
53
52
  const uniq = arr => Array.from(new Set(arr));
@@ -438,7 +437,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
438
437
  increase_transaction_id: getFunding.id,
439
438
  increase_transaction_vout: getFunding.vout,
440
439
  increase_witness_script: getFunding.script,
441
- is_private: channel.is_private,
440
+ is_private: askForChangeDetails.is_private,
442
441
  open_transaction: sendBasicRequest,
443
442
  partner_public_key: askForChangeDetails.partner_public_key,
444
443
  transaction_id: channel.transaction_id,
@@ -4,6 +4,7 @@ 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();
7
8
  const idHexLength = 64;
8
9
  const tooLarge = BigInt(Number.MAX_SAFE_INTEGER);
9
10
 
@@ -11,6 +12,7 @@ const findChannelRecord = records => records.find(n => n.type === '2');
11
12
  const findDecreaseRecord = records => records.find(n => n.type === '3');
12
13
  const findIncreaseRecord = records => records.find(n => n.type === '4');
13
14
  const findRequestIdRecord = records => records.find(n => n.type === '1');
15
+ const findTypeRecord = records => records.find(n => n.type === '5');
14
16
  const findVersionRecord = records => records.find(n => n.type === '0');
15
17
 
16
18
  /** Parse a capacity change request
@@ -31,6 +33,7 @@ const findVersionRecord = records => records.find(n => n.type === '0');
31
33
  from: <From Node Public Key Id Hex String>
32
34
  id: <Change Request Id Hex String>
33
35
  [increase]: <Add Capacity Tokens Number>
36
+ [type]: <Intended Replacement Channel Channel Type Flags Number>
34
37
  }
35
38
  }
36
39
  */
@@ -52,6 +55,7 @@ module.exports = ({from, records}) => {
52
55
  return {};
53
56
  }
54
57
 
58
+ // Make sure the channel id is a regular one
55
59
  try {
56
60
  chanFormat({id: channelRecord.value});
57
61
  } catch (err) {
@@ -67,6 +71,7 @@ module.exports = ({from, records}) => {
67
71
 
68
72
  const decreaseRecord = findDecreaseRecord(records);
69
73
  const increaseRecord = findIncreaseRecord(records);
74
+ const typeRecord = findTypeRecord(records);
70
75
 
71
76
  // Exit early when there is a change in both directions
72
77
  if (!!decreaseRecord && !!increaseRecord) {
@@ -75,10 +80,11 @@ module.exports = ({from, records}) => {
75
80
 
76
81
  const {channel} = chanFormat({id: channelRecord.value});
77
82
  const id = idRecord.value;
83
+ const type = typeRecord ? hexAsNumber(typeRecord.value) : undefined;
78
84
 
79
85
  // Exit early when there is no decrease or increase
80
86
  if (!decreaseRecord && !increaseRecord) {
81
- return {request: {channel, from, id}};
87
+ return {request: {channel, from, id, type}};
82
88
  }
83
89
 
84
90
  const decrease = decodeNumber((decreaseRecord || defaultRecord).value);
@@ -94,6 +100,7 @@ module.exports = ({from, records}) => {
94
100
  channel,
95
101
  from,
96
102
  id,
103
+ type,
97
104
  decrease: Number(decrease) || undefined,
98
105
  increase: Number(increase) || undefined,
99
106
  },
@@ -1,15 +1,17 @@
1
1
  const asyncAuto = require('async/auto');
2
- const asyncDoUntil = require('async/doUntil');
2
+ const asyncEach = require('async/each');
3
3
  const asyncReflect = require('async/reflect');
4
4
  const {broadcastChainTransaction} = require('ln-service');
5
5
  const {cancelPendingChannel} = require('ln-service');
6
+ const {deletePendingChannel} = require('ln-service');
6
7
  const {fundPendingChannels} = require('ln-service');
7
- const {getChainTransactions} = require('ln-service');
8
8
  const {getChannels} = require('ln-service');
9
9
  const {getClosedChannels} = require('ln-service');
10
10
  const {getHeight} = require('ln-service');
11
+ const {getPendingChannels} = require('ln-service');
11
12
  const {returnResult} = require('asyncjs-util');
12
13
  const {signTransaction} = require('ln-service');
14
+ const {subscribeToBlocks} = require('ln-service');
13
15
  const {Transaction} = require('bitcoinjs-lib');
14
16
  const {transactionAsPsbt} = require('psbt');
15
17
 
@@ -21,6 +23,7 @@ const {serviceTypeSignCapacityChange} = require('./../service_types');
21
23
 
22
24
  const capacityChangeIdType = '0';
23
25
  const findSignatureRecord = records => records.find(n => n.type === '1');
26
+ const {fromHex} = Transaction;
24
27
  const fuzzHeight = 3;
25
28
  const maxSigHexLength = 146;
26
29
  const peerRequestTimeoutMs = 1000 * 60 * 10;
@@ -35,9 +38,10 @@ const unsignedTransactionType = '1';
35
38
  bitcoinjs_network: <BitcoinJS Network Name String>
36
39
  channel: <Channel to Change Standard Format Channel Id String>
37
40
  decrease: [{
38
- address: <Spend to Chain Address String>
39
- output: <Spend to Output Script Hex String>
40
- tokens: <Spend Value Number>
41
+ [address]: <Decrease Address String>
42
+ [node]: <Create Channel with Node With Public Key Hex String>
43
+ [output]: <Output Script Hex String>
44
+ tokens: <Decrease Tokens Number>
41
45
  }]
42
46
  id: <Capacity Change Request Id Hex String>
43
47
  is_private: <Channel is Private Bool>
@@ -109,10 +113,7 @@ module.exports = (args, cbk) => {
109
113
  return cbk();
110
114
  },
111
115
 
112
- // Get the starting height
113
- getHeight: ['validate', ({}, cbk) => getHeight({lnd: args.lnd}, cbk)],
114
-
115
- // Close the current channal and get the unsigned replacement
116
+ // Close the current channel and get the unsigned replacement
116
117
  getReplacement: ['validate', ({}, cbk) => {
117
118
  return getCapacityReplacement({
118
119
  add_funds_transaction_id: args.increase_transaction_id,
@@ -122,6 +123,7 @@ module.exports = (args, cbk) => {
122
123
  id: args.channel,
123
124
  is_private: args.is_private,
124
125
  increase: args.increase,
126
+ is_private: args.is_private,
125
127
  lnd: args.lnd,
126
128
  open_transaction: args.open_transaction,
127
129
  transaction_id: args.transaction_id,
@@ -130,9 +132,9 @@ module.exports = (args, cbk) => {
130
132
  cbk);
131
133
  }],
132
134
 
133
- // Replacement channel outpoint
135
+ // The replacement channel outpoint will be returned as a result
134
136
  replacement: ['getReplacement', ({getReplacement}, cbk) => {
135
- args.logger.info({pending_chan_id: getReplacement.pending_channel_id});
137
+ args.logger.info({proposal_id: getReplacement.pending_channel_id});
136
138
 
137
139
  return cbk(null, {
138
140
  transaction_id: getReplacement.transaction_id,
@@ -163,8 +165,8 @@ module.exports = (args, cbk) => {
163
165
  cbk);
164
166
  }],
165
167
 
166
- // Use the unsigned replacement transaction to "fund" the pending channel
167
- fundWithReplacement: [
168
+ // Derive the funding PSBT to use for funding
169
+ funding: [
168
170
  'getReplacement',
169
171
  'signAddFunds',
170
172
  ({getReplacement, signAddFunds}, cbk) =>
@@ -182,10 +184,22 @@ module.exports = (args, cbk) => {
182
184
  witness_script: getReplacement.witness_script,
183
185
  });
184
186
 
185
- // Create the commitment transaction with the peer on the replacement
187
+ return cbk(null, psbt);
188
+ }],
189
+
190
+ // Use the unsigned replacement transaction to "fund" the pending channel
191
+ fundWithReplacement: [
192
+ 'funding',
193
+ 'getReplacement',
194
+ ({funding, getReplacement}, cbk) =>
195
+ {
196
+ const replacement = getReplacement.unsigned_transaction;
197
+
198
+ args.logger.info({funding_with_replacement_tx: replacement});
199
+
186
200
  return fundPendingChannels({
201
+ funding,
187
202
  channels: [getReplacement.pending_channel_id],
188
- funding: psbt,
189
203
  lnd: args.lnd,
190
204
  },
191
205
  cbk);
@@ -193,8 +207,8 @@ module.exports = (args, cbk) => {
193
207
 
194
208
  // Send the final go ahead signal now that a proposal is made
195
209
  getPeerSignature: [
196
- 'getReplacement',
197
210
  'fundWithReplacement',
211
+ 'getReplacement',
198
212
  asyncReflect(({getReplacement}, cbk) =>
199
213
  {
200
214
  return makePeerRequest({
@@ -228,49 +242,86 @@ module.exports = (args, cbk) => {
228
242
  return cbk([503, 'ExpectedSignatureRecordFromPeerForChange']);
229
243
  }
230
244
 
245
+ args.logger.info({peer_cosigned_capacity_change: true});
246
+
231
247
  return cbk(null, record.value);
232
248
  });
233
249
  })],
234
250
 
235
- // Cancel the funding proposal when there is an error
251
+ // Fund the additional other channel opens if present
252
+ fundAdditionalOpens: [
253
+ 'funding',
254
+ 'getPeerSignature',
255
+ 'getReplacement',
256
+ asyncReflect(({funding, getPeerSignature, getReplacement}, cbk) =>
257
+ {
258
+ // Exit early when there was an error getting the peer signature
259
+ if (!!getPeerSignature.error) {
260
+ return cbk();
261
+ }
262
+
263
+ // Exit early when there is nothing else to fund
264
+ if (!getReplacement.open_pending_ids.length) {
265
+ return cbk();
266
+ }
267
+
268
+ // Funding for the additional opens is separated for smoother canceling
269
+ return fundPendingChannels({
270
+ funding,
271
+ channels: getReplacement.open_pending_ids,
272
+ lnd: args.lnd,
273
+ },
274
+ cbk);
275
+ })],
276
+
277
+ // Cancel the unfunded channel opens when there is an error
236
278
  cancelFunding: [
237
279
  'getPeerSignature',
238
280
  'getReplacement',
239
281
  ({getPeerSignature, getReplacement}, cbk) =>
240
282
  {
241
- // Exit early when there is no need to cancel
283
+ // Exit early when there was an error getting the peer signature
242
284
  if (!getPeerSignature.error) {
243
285
  return cbk();
244
286
  }
245
287
 
246
- return cancelPendingChannel({
247
- id: getReplacement.pending_channel_id,
248
- lnd: args.lnd,
249
- },
250
- err => {
251
- if (!!err) {
252
- return cbk([503, 'FailedToCancelPendingChannel', {err}]);
253
- }
288
+ const ids = getReplacement.open_pending_ids;
254
289
 
255
- // Return the original error
256
- return cbk(getPeerSignature.error);
257
- });
290
+ return asyncEach(ids, asyncReflect((id, cbk) => {
291
+ return cancelPendingChannel({id, lnd: args.lnd}, cbk);
292
+ }),
293
+ cbk);
258
294
  }],
259
295
 
260
296
  // Broadcast the new funding transaction using the peer's signature
261
297
  confirmFunding: [
262
- 'getHeight',
298
+ 'fundAdditionalOpens',
263
299
  'getPeerSignature',
264
300
  'getReplacement',
265
301
  'signAddFunds',
266
- ({getHeight, getPeerSignature, getReplacement, signAddFunds}, cbk) =>
302
+ ({
303
+ fundAdditionalOpens,
304
+ getPeerSignature,
305
+ getReplacement,
306
+ signAddFunds,
307
+ },
308
+ cbk) =>
267
309
  {
268
- // Exit early when there was an error getting the peer signature
310
+ // When there is no peer signature, the force close must confirm
269
311
  if (!!getPeerSignature.error) {
270
- return cbk();
312
+ return cbk(null, {is_success: false});
313
+ }
314
+
315
+ // Do not broadcast a replacement when additional opens fail
316
+ if (!!fundAdditionalOpens.error) {
317
+ return cbk(null, {is_success: false});
271
318
  }
272
319
 
273
320
  const [addFundsSignature] = signAddFunds.signatures || [];
321
+ const openOutputs = fromHex(args.open_transaction).outs;
322
+ const txId = fromHex(getReplacement.unsigned_transaction).getId();
323
+
324
+ const outScript = openOutputs[args.transaction_vout].script;
274
325
 
275
326
  // Insert the peer signature to get the final signed funding tx
276
327
  const {transaction} = finalizeCapacityReplacement({
@@ -287,75 +338,140 @@ module.exports = (args, cbk) => {
287
338
 
288
339
  args.logger.info({publishing_new_channel_transaction: transaction});
289
340
 
290
- return asyncDoUntil(
291
- cbk => {
292
- // Try and override the force close tx using the replacement
293
- return broadcastChainTransaction({
294
- transaction,
295
- after: getHeight.current_block_height - fuzzHeight,
296
- lnd: args.lnd,
297
- },
298
- cbk);
299
- },
300
- (broadcast, cbk) => {
301
- args.logger.info({waiting_for_confirmation: broadcast.id});
341
+ // Listen to new blocks to wait for the channel change confirmation
342
+ const sub = subscribeToBlocks({lnd: args.lnd});
302
343
 
303
- // Rebroadcast until the transaction is confirmed
304
- return getChainTransactions({lnd: args.lnd}, (err, res) => {
305
- if (!!err) {
306
- return cbk(err);
307
- }
344
+ // Fail with error when the blocks subscription is lost
345
+ sub.on('error', err => {
346
+ sub.removeAllListeners();
347
+
348
+ return cbk([503, 'LostBlockchainSubscription', {err}]);
349
+ });
350
+
351
+ // Publish the new channel transaction when a block is received
352
+ sub.on('block', async () => {
353
+ // Broadcast the channel replacement tx
354
+ try {
355
+ await broadcastChainTransaction({transaction, lnd: args.lnd});
356
+
357
+ args.logger.info({published_new_channel_tx: txId});
358
+ } catch (err) {
359
+ args.logger.error({err});
360
+ }
361
+
362
+ // Look for the new channel to see if the change succeeded
363
+ try {
364
+ const {channels} = await getChannels({lnd: args.lnd});
308
365
 
309
- const confirmed = res.transactions.filter(n => !!n.is_confirmed);
366
+ const channel = channels.find(n => n.transaction_id === txId);
310
367
 
311
- // Find the confirmed tx
312
- const tx = confirmed.find(tx => tx.id === broadcast.id);
368
+ // Replacement is present but not yet active
369
+ if (!!channel && !channel.is_active) {
370
+ args.logger.info({waiting_for_channel_to_activate: true});
371
+ }
313
372
 
314
- if (!!tx) {
315
- return cbk(null, true);
373
+ // The new channel confirmed successfully and so the change worked
374
+ if (!!channel && !!channel.is_active) {
375
+ sub.removeAllListeners();
376
+
377
+ return cbk(null, {is_success: true});
378
+ }
379
+ } catch (err) {
380
+ args.logger.error({err});
381
+ }
382
+
383
+ // Also look for a force closed channel to see if the change failed
384
+ try {
385
+ const {channels} = await getClosedChannels({lnd: args.lnd});
386
+ const chainTip = await getHeight({lnd: args.lnd});
387
+
388
+ const currentHeight = chainTip.current_block_height;
389
+
390
+ const channel = channels.find(channel => {
391
+ // The closed channel must have a closed tx id
392
+ if (!channel.close_transaction_id) {
393
+ return false;
316
394
  }
317
395
 
318
- // Find the replacing tx
319
- const replacing = confirmed.find(tx => {
320
- return tx.id === getReplacement.force_close_tx_id;
321
- });
322
-
323
- // Cancel the pending channel if the force close confirms
324
- if (!!replacing) {
325
- return cancelPendingChannel({
326
- id: getReplacement.pending_channel_id,
327
- lnd: args.lnd,
328
- },
329
- () => {
330
- return cbk([503, 'FailedToReplaceForceCloseTransaction']);
331
- });
396
+ // Only consider channels that are conclusively closed
397
+ if (currentHeight - channel.close_confirm_height < fuzzHeight) {
398
+ return false;
332
399
  }
333
400
 
334
- return getChannels({
335
- lnd: args.lnd,
336
- partner_public_key: args.partner_public_key,
337
- },
338
- (err, res) => {
339
- if (!!err) {
340
- return cbk(err);
341
- }
342
-
343
- const channel = res.channels.find(channel => {
344
- return channel.transaction_id === broadcast.id
345
- });
346
-
347
- // Stop broadcasting when a channel shows up
348
- if (!!channel) {
349
- return cbk(null, true);
350
- }
351
-
352
- // Rebroadcast after a delay
353
- return setTimeout(() => cbk(null, false), rebroadcastDelayMs);
354
- });
401
+ // Find close channel matching the channel outpoint
402
+ if (channel.transaction_id !== args.transaction_id) {
403
+ return false;
404
+ }
405
+
406
+ return channel.transaction_vout === args.transaction_vout;
355
407
  });
408
+
409
+ // The old channel force closed and so the change fully failed
410
+ if (!!channel && channel.close_transaction_id !== txId) {
411
+ sub.removeAllListeners();
412
+
413
+ return cbk(null, {is_success: false});
414
+ }
415
+ } catch (err) {
416
+ args.logger.error({err});
417
+ }
418
+
419
+ return;
420
+ });
421
+
422
+ return;
423
+ }],
424
+
425
+ // Get pending channels to clean up if there was a failure
426
+ getPending: ['confirmFunding', ({confirmFunding}, cbk) => {
427
+ // Exit early when there was no failure
428
+ if (!!confirmFunding.is_success) {
429
+ return cbk();
430
+ }
431
+
432
+ return getPendingChannels({lnd: args.lnd}, cbk);
433
+ }],
434
+
435
+ // Clean up pending channels that will never confirm
436
+ cleanPendingChannels: [
437
+ 'confirmFunding',
438
+ 'getPending',
439
+ 'getReplacement',
440
+ ({confirmFunding, getPending, getReplacement}, cbk) =>
441
+ {
442
+ // Exit early when the channel activated
443
+ if (!!confirmFunding.is_success) {
444
+ args.logger.info({channel_capacity_changed: true});
445
+
446
+ return cbk();
447
+ }
448
+
449
+ const replaceId = fromHex(getReplacement.unsigned_transaction).getId();
450
+
451
+ // Collect all the pending channels that depend on the replacement
452
+ const pending = getPending.pending_channels
453
+ .filter(n => !!n.is_opening)
454
+ .filter(n => n.transaction_id === replaceId);
455
+
456
+ // Delete the pending channels that can never confirm
457
+ return asyncEach(pending, (channel, cbk) => {
458
+ return deletePendingChannel({
459
+ confirmed_transaction: getReplacement.force_close_tx,
460
+ lnd: args.lnd,
461
+ pending_transaction: getReplacement.unsigned_transaction,
462
+ pending_transaction_vout: channel.transaction_vout,
356
463
  },
357
- cbk
358
- );
464
+ err => {
465
+ if (!!err) {
466
+ args.logger.error({err});
467
+ }
468
+
469
+ return cbk();
470
+ });
471
+ },
472
+ () => {
473
+ return cbk([503, 'FailedToChangeChannelCapacity']);
474
+ });
359
475
  }],
360
476
  },
361
477
  returnResult({reject, resolve, of: 'replacement'}, cbk));