paid-services 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/capacity/accept_capacity_change.js +284 -0
- package/capacity/ask_for_change_details.js +505 -0
- package/capacity/ask_for_decrease.js +185 -0
- package/capacity/assemble_replacement_tx.js +171 -0
- package/capacity/capacity_change_requests.js +76 -0
- package/capacity/change_channel_capacity.js +183 -0
- package/capacity/encode_change_request.js +55 -0
- package/capacity/encode_sign_capacity_response.js +32 -0
- package/capacity/fee_for_replacement.js +108 -0
- package/capacity/finalize_capacity_replacement.js +67 -0
- package/capacity/get_capacity_change_requests.js +98 -0
- package/capacity/get_capacity_replacement.js +371 -0
- package/capacity/get_replacement_signature.js +232 -0
- package/capacity/index.js +4 -0
- package/capacity/initiate_capacity_change.js +506 -0
- package/capacity/interim_replacement_psbt.js +57 -0
- package/capacity/is_open_witness_script.js +40 -0
- package/capacity/parse_accept_request.js +58 -0
- package/capacity/parse_accept_response.js +52 -0
- package/capacity/parse_capacity_change_request.js +101 -0
- package/capacity/parse_sign_capacity_request.js +59 -0
- package/capacity/propose_capacity_change.js +361 -0
- package/capacity/service_types.json +6 -0
- package/capacity/sign_capacity_replacement.js +119 -0
- package/capacity/witness_script_from_close_txs.js +50 -0
- package/index.js +2 -0
- package/p2p/encode_peer_response.js +0 -1
- package/p2p/make_peer_request.js +3 -3
- package/package.json +6 -4
- package/test/capacity/test_assemble_replacement_tx.js +46 -0
- package/test/capacity/test_capacity_change_requests.js +54 -0
- package/test/capacity/test_encode_change_request.js +49 -0
- package/test/capacity/test_encode_sign_capacity_response.js +47 -0
- package/test/capacity/test_fee_for_replacement.js +40 -0
- package/test/capacity/test_finalize_capacity_replacement.js +45 -0
- package/test/capacity/test_interim_replacement_psbt.js +44 -0
- package/test/capacity/test_is_open_witness_script.js +40 -0
- package/test/capacity/test_parse_accept_request.js +42 -0
- package/test/capacity/test_parse_capacity_change_request.js +51 -0
- package/test/capacity/test_parse_sign_capacity_request.js +49 -0
- package/test/capacity/test_witness_script_from_close_txs.js +53 -0
- package/test/integration/test_accept_capacity_change.js +152 -0
- package/test/integration/test_change_channel_capacity.js +196 -0
- package/test/integration/test_decrease_channel_capacity.js +206 -0
- package/test/integration/test_increase_channel_capacity.js +197 -0
- package/test/integration/test_sign_capacity_replacement.js +142 -0
- package/test/p2p/test_encode_peer_request.js +53 -0
- package/test/p2p/test_encode_peer_response.js +38 -0
- package/test/p2p/test_encode_response_code.js +48 -0
- package/trades/network_from_network_record.js +6 -0
- package/trades/network_record_from_request.js +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {getChainTransactions} = require('ln-service');
|
|
3
|
+
const {getChannel} = require('ln-service');
|
|
4
|
+
const {getChannels} = require('ln-service');
|
|
5
|
+
const {returnResult} = require('asyncjs-util');
|
|
6
|
+
const {stopAllHtlcs} = require('ln-sync');
|
|
7
|
+
const {Transaction} = require('bitcoinjs-lib');
|
|
8
|
+
|
|
9
|
+
const encodeSignCapacityResponse = require('./encode_sign_capacity_response');
|
|
10
|
+
const getReplacementSignature = require('./get_replacement_signature');
|
|
11
|
+
const {makePeerRequest} = require('./../p2p');
|
|
12
|
+
const parseAcceptResponse = require('./parse_accept_response');
|
|
13
|
+
const parseSignCapacityRequest = require('./parse_sign_capacity_request');
|
|
14
|
+
const {servicePeerRequests} = require('./../p2p');
|
|
15
|
+
const {serviceTypeAcceptCapacityChange} = require('./service_types');
|
|
16
|
+
const {serviceTypeSignCapacityChange} = require('./service_types');
|
|
17
|
+
const {serviceTypeWaitOnConfirmation} = require('./service_types');
|
|
18
|
+
|
|
19
|
+
const acceptChangeTimeoutMs = 1000 * 45;
|
|
20
|
+
const bufferAsHex = buffer => buffer.toString('hex');
|
|
21
|
+
const findId = records => (records.find(n => n.type === '0') || {}).value;
|
|
22
|
+
const {fromHex} = Transaction;
|
|
23
|
+
const idType = '0';
|
|
24
|
+
const openTxType = '1';
|
|
25
|
+
|
|
26
|
+
/** Accept a request from a peer to change a channel's capacity
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
channel: <Standard Format Channel Id String>
|
|
30
|
+
from: <Accept Request From Public Key Hex String>
|
|
31
|
+
id: <Request Id Hex String>
|
|
32
|
+
[increase]: <Increase Tokens Number>
|
|
33
|
+
lnd: <Authenticated LND API Object>
|
|
34
|
+
logger: <Winston Logger Object>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@returns via cbk or Promise
|
|
38
|
+
{
|
|
39
|
+
base_fee_mtokens: <Original Forwarding Base Fee Millitokens String>
|
|
40
|
+
cltv_delta: <Original Forwarding CLTV Delta Number>
|
|
41
|
+
fee_rate: <Original Forwarding Parts Per Million Fee Rate Number>
|
|
42
|
+
transaction_id: <Replacement Channel Transaction Id Hex String>
|
|
43
|
+
transaction_vout: <Replacement Channel Transaction Output Index Number>
|
|
44
|
+
}
|
|
45
|
+
*/
|
|
46
|
+
module.exports = ({channel, from, id, increase, lnd, logger}, cbk) => {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
return asyncAuto({
|
|
49
|
+
// Check arguments
|
|
50
|
+
validate: cbk => {
|
|
51
|
+
if (!channel) {
|
|
52
|
+
return cbk([400, 'ExpectedChannelIdToAcceptCapacityChange']);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!from) {
|
|
56
|
+
return cbk([400, 'ExpectedFromPublicKeyToAcceptCapacityChange']);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!id) {
|
|
60
|
+
return cbk([400, 'ExpectedRequestIdToAcceptCapacityChange']);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!lnd) {
|
|
64
|
+
return cbk([400, 'ExpectedAuthenticatedLndToAcceptCapacityChange']);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!logger) {
|
|
68
|
+
return cbk([400, 'ExpectedLoggerObjectToAcceptCapacityChange']);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return cbk();
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// Get the fee policies on the channel to set it back on success
|
|
75
|
+
getChannel: ['validate', ({}, cbk) => {
|
|
76
|
+
return getChannel({lnd, id: channel}, cbk);
|
|
77
|
+
}],
|
|
78
|
+
|
|
79
|
+
// Get all channel ids with the peer in order to stop HTLCs on them
|
|
80
|
+
getChannels: ['validate', ({}, cbk) => {
|
|
81
|
+
return getChannels({lnd, partner_public_key: from}, cbk);
|
|
82
|
+
}],
|
|
83
|
+
|
|
84
|
+
// Get chain transactions to look for the channel open tx
|
|
85
|
+
getChainTxs: ['validate', ({}, cbk) => {
|
|
86
|
+
return getChainTransactions({lnd}, cbk);
|
|
87
|
+
}],
|
|
88
|
+
|
|
89
|
+
// Policy for the channel
|
|
90
|
+
policy: ['getChannel', ({getChannel}, cbk) => {
|
|
91
|
+
const policy = getChannel.policies.find(n => n.public_key !== from);
|
|
92
|
+
|
|
93
|
+
if (!policy.cltv_delta) {
|
|
94
|
+
return cbk([404, 'FailedToFindChannelRoutingPolicyDetails']);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return cbk(null, {
|
|
98
|
+
base_fee_mtokens: policy.base_fee_mtokens,
|
|
99
|
+
cltv_delta: policy.cltv_delta,
|
|
100
|
+
fee_rate: policy.fee_rate,
|
|
101
|
+
transaction_id: getChannel.transaction_id,
|
|
102
|
+
transaction_vout: getChannel.transaction_vout,
|
|
103
|
+
});
|
|
104
|
+
}],
|
|
105
|
+
|
|
106
|
+
// Send the initial proposal acceptance to the peer
|
|
107
|
+
acceptChange: [
|
|
108
|
+
'getChainTxs',
|
|
109
|
+
'getChannel',
|
|
110
|
+
'getChannels',
|
|
111
|
+
'policy',
|
|
112
|
+
({getChainTxs, getChannel}, cbk) =>
|
|
113
|
+
{
|
|
114
|
+
const records = [{type: idType, value: id}];
|
|
115
|
+
|
|
116
|
+
const tx = getChainTxs.transactions.find(transaction => {
|
|
117
|
+
return transaction.id === getChannel.transaction_id;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const txRecordValue = !!tx && !!tx.transaction ? tx.transaction : null;
|
|
121
|
+
|
|
122
|
+
if (!!txRecordValue) {
|
|
123
|
+
records.push({type: openTxType, value: txRecordValue});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Kick off the capacity change
|
|
127
|
+
return makePeerRequest({
|
|
128
|
+
lnd,
|
|
129
|
+
records,
|
|
130
|
+
timeout: acceptChangeTimeoutMs,
|
|
131
|
+
to: from,
|
|
132
|
+
type: serviceTypeAcceptCapacityChange,
|
|
133
|
+
},
|
|
134
|
+
(err, res) => {
|
|
135
|
+
if (!!err) {
|
|
136
|
+
return cbk(err);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const {transaction} = parseAcceptResponse({
|
|
141
|
+
records: res.records,
|
|
142
|
+
transaction_id: getChannel.transaction_id,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (!transaction && !txRecordValue) {
|
|
146
|
+
return cbk([404, 'MissingTransactionOpenRawTxData']);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return cbk(null, {transaction: transaction || txRecordValue});
|
|
150
|
+
} catch (err) {
|
|
151
|
+
return cbk([503, err.message]);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}],
|
|
155
|
+
|
|
156
|
+
// Stop HTLCs with the peer until the channel is closed
|
|
157
|
+
stopHtlcs: ['getChannels', ({getChannels}, cbk) => {
|
|
158
|
+
return stopAllHtlcs({
|
|
159
|
+
lnd,
|
|
160
|
+
id: channel,
|
|
161
|
+
ids: getChannels.channels.map(n => n.id),
|
|
162
|
+
peer: from,
|
|
163
|
+
},
|
|
164
|
+
cbk);
|
|
165
|
+
}],
|
|
166
|
+
|
|
167
|
+
// Lookup the open transaction and get the output script
|
|
168
|
+
output: [
|
|
169
|
+
'acceptChange',
|
|
170
|
+
'getChannel',
|
|
171
|
+
({acceptChange, getChannel}, cbk) =>
|
|
172
|
+
{
|
|
173
|
+
const {outs} = fromHex(acceptChange.transaction);
|
|
174
|
+
|
|
175
|
+
const output = outs[getChannel.transaction_vout];
|
|
176
|
+
|
|
177
|
+
if (!output) {
|
|
178
|
+
return cbk([503, 'ExpectedOpenTxFundingTransactionOutput']);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return cbk(null, bufferAsHex(output.script));
|
|
182
|
+
}],
|
|
183
|
+
|
|
184
|
+
// Ask to change capacity and return the final signature when needed
|
|
185
|
+
changeCapacity: ['output', 'policy', ({output, policy}, cbk) => {
|
|
186
|
+
logger.info({starting_capacity_change: true});
|
|
187
|
+
|
|
188
|
+
const service = servicePeerRequests({lnd});
|
|
189
|
+
|
|
190
|
+
// Listen for close-and-replace signals
|
|
191
|
+
service.request({type: serviceTypeSignCapacityChange}, (req, res) => {
|
|
192
|
+
// Exit early when the request is from a different peer/change
|
|
193
|
+
if (req.from !== from || findId(req.records) !== id) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const {failure, success} = res;
|
|
198
|
+
|
|
199
|
+
const parameters = {
|
|
200
|
+
increase,
|
|
201
|
+
id: policy.transaction_id,
|
|
202
|
+
records: req.records,
|
|
203
|
+
vout: policy.transaction_vout,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Check to make sure that the request records make sense
|
|
207
|
+
try {
|
|
208
|
+
parseSignCapacityRequest(parameters);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
logger.error({err: [503, err.message]});
|
|
211
|
+
|
|
212
|
+
return failure([503, 'FailedToParseSignChangeRequest']);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Trigger the replacement flow and sign the unsigned tx
|
|
216
|
+
return getReplacementSignature({
|
|
217
|
+
channel,
|
|
218
|
+
lnd,
|
|
219
|
+
output,
|
|
220
|
+
id: parameters.id,
|
|
221
|
+
unsigned: parseSignCapacityRequest(parameters).unsigned,
|
|
222
|
+
vout: parameters.vout,
|
|
223
|
+
},
|
|
224
|
+
(err, res) => {
|
|
225
|
+
if (!!err) {
|
|
226
|
+
logger.error({err});
|
|
227
|
+
|
|
228
|
+
return failure([503, 'UnexpectedErrorGettingSignature']);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Stop listening for sign capacity change requests
|
|
232
|
+
service.stop({});
|
|
233
|
+
|
|
234
|
+
// Encode the signature into records
|
|
235
|
+
const {records} = encodeSignCapacityResponse({
|
|
236
|
+
id: parameters.id,
|
|
237
|
+
signature: res.signature,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Send the replacement signature for the change request
|
|
241
|
+
success({records});
|
|
242
|
+
|
|
243
|
+
logger.info({new_channel_transaction_id: res.transaction_id});
|
|
244
|
+
|
|
245
|
+
// Return the replacement funding outpoint
|
|
246
|
+
return cbk(null, {
|
|
247
|
+
transaction_id: res.transaction_id,
|
|
248
|
+
transaction_vout: res.transaction_vout,
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Listen for wait-for-confirmation signals
|
|
254
|
+
service.request({type: serviceTypeWaitOnConfirmation}, (req, res) => {
|
|
255
|
+
// Exit early when the request is from a different peer/change
|
|
256
|
+
if (req.from !== from || findId(req.records) !== id) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
logger.info({peer_is_looking_for_confirmation: new Date()});
|
|
261
|
+
|
|
262
|
+
// Return an ack that the waiting signal was heard
|
|
263
|
+
return res.success({});
|
|
264
|
+
});
|
|
265
|
+
}],
|
|
266
|
+
|
|
267
|
+
// Accepted the capacity change
|
|
268
|
+
accepted: [
|
|
269
|
+
'changeCapacity',
|
|
270
|
+
'policy',
|
|
271
|
+
({changeCapacity, policy}, cbk) =>
|
|
272
|
+
{
|
|
273
|
+
return cbk(null, {
|
|
274
|
+
base_fee_mtokens: policy.base_fee_mtokens,
|
|
275
|
+
cltv_delta: policy.cltv_delta,
|
|
276
|
+
fee_rate: policy.fee_rate,
|
|
277
|
+
transaction_id: changeCapacity.transaction_id,
|
|
278
|
+
transaction_vout: changeCapacity.transaction_vout,
|
|
279
|
+
});
|
|
280
|
+
}],
|
|
281
|
+
},
|
|
282
|
+
returnResult({reject, resolve, of: 'accepted'}, cbk));
|
|
283
|
+
});
|
|
284
|
+
};
|