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.
- package/.prettierignore +2 -0
- package/CHANGELOG.md +16 -0
- package/capacity/accept_capacity_change.js +2 -0
- package/capacity/ask_for_change_details.js +41 -9
- package/capacity/ask_for_decrease.js +167 -49
- package/capacity/capacity_change_requests.js +9 -0
- package/capacity/change_channel_capacity.js +7 -1
- package/capacity/encode_change_request.js +8 -1
- package/capacity/fee_for_replacement.js +3 -7
- package/capacity/get_capacity_change_requests.js +1 -0
- package/capacity/get_capacity_replacement.js +56 -7
- package/capacity/initiate_capacity_change.js +1 -2
- package/capacity/parse_capacity_change_request.js +8 -1
- package/capacity/propose_capacity_change.js +208 -92
- package/capacity/propose_channel_open.js +54 -0
- package/package.json +9 -9
- package/services/response_for_relay.js +0 -1
- package/test/capacity/test_capacity_change_requests.js +1 -0
- package/test/capacity/test_encode_change_request.js +5 -0
- package/test/capacity/test_parse_capacity_change_request.js +1 -0
- package/test/integration/test_accept_capacity_change.js +3 -4
- package/test/integration/test_change_channel_capacity.js +8 -1
- package/test/integration/test_decrease_channel_capacity.js +23 -8
- package/test/integration/test_increase_channel_capacity.js +4 -1
- package/test/integration/test_split_channel_capacity.js +241 -0
- package/trades/accept_trade.js +12 -3
- package/trades/create_trade.js +82 -50
- package/trades/decode_anchored_trade.js +56 -0
- package/trades/encode_anchored_trade.js +35 -0
- package/trades/finalize_trade_secret.js +7 -0
- package/trades/find_trade.js +28 -3
- package/trades/manage_trade.js +15 -4
- package/trades/manage_trades.js +209 -2
- package/trades/request_trade_by_id.js +7 -1
- package/trades/service_anchored_trades.js +181 -0
- package/trades/service_open_trade.js +172 -0
- package/trades/service_trade_requests.js +106 -35
- 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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
|
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: <
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
135
|
+
// The replacement channel outpoint will be returned as a result
|
|
134
136
|
replacement: ['getReplacement', ({getReplacement}, cbk) => {
|
|
135
|
-
args.logger.info({
|
|
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
|
-
//
|
|
167
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
256
|
-
return
|
|
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
|
-
'
|
|
298
|
+
'fundAdditionalOpens',
|
|
263
299
|
'getPeerSignature',
|
|
264
300
|
'getReplacement',
|
|
265
301
|
'signAddFunds',
|
|
266
|
-
({
|
|
302
|
+
({
|
|
303
|
+
fundAdditionalOpens,
|
|
304
|
+
getPeerSignature,
|
|
305
|
+
getReplacement,
|
|
306
|
+
signAddFunds,
|
|
307
|
+
},
|
|
308
|
+
cbk) =>
|
|
267
309
|
{
|
|
268
|
-
//
|
|
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
|
-
|
|
291
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
366
|
+
const channel = channels.find(n => n.transaction_id === txId);
|
|
310
367
|
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
315
|
-
|
|
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
|
-
//
|
|
319
|
-
|
|
320
|
-
return
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
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));
|