ln-service 53.7.1 → 53.7.2
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 +1 -1
- package/package.json +4 -4
- package/test/integration/test_add_peer.js +3 -1
- package/test/integration/test_delete_pending_channel.js +5 -2
- package/test/integration/test_get_wallet_info.js +27 -23
- package/test/integration/test_open_channels.js +21 -14
- package/test/integration/test_propose_channel.js +438 -421
- package/test/integration/test_subscribe_to_peers.js +23 -19
- package/test/routerrpc-integration/test_get_route_through_hops.js +10 -8
- package/test/routerrpc-integration/test_pay_via_payment_details.js +118 -110
- package/test/routerrpc-integration/test_probe_for_route.js +75 -71
- package/test/routerrpc-integration/test_subscribe_to_forwards.js +508 -504
- package/test/walletrpc-integration/test_fund_psbt.js +4 -1
- package/test/walletrpc-integration/test_partially_sign_psbt.js +12 -5
- package/test/walletrpc-integration/test_sign_psbt.js +4 -1
|
@@ -10,6 +10,7 @@ const {script} = require('bitcoinjs-lib');
|
|
|
10
10
|
const signPsbtWithKey = require('psbt').signPsbt;
|
|
11
11
|
const {spawnLightningCluster} = require('ln-docker-daemons');
|
|
12
12
|
const {test} = require('@alexbosworth/tap');
|
|
13
|
+
const tinysecp = require('tiny-secp256k1');
|
|
13
14
|
const {Transaction} = require('bitcoinjs-lib');
|
|
14
15
|
const {updatePsbt} = require('psbt');
|
|
15
16
|
|
|
@@ -49,447 +50,463 @@ const times = 300;
|
|
|
49
50
|
|
|
50
51
|
// Proposing a cooperative delay channel should open a cooperative delay chan
|
|
51
52
|
test(`Propose a channel with a coop delay`, async ({end, equal, ok}) => {
|
|
53
|
+
const ecp = (await import('ecpair')).ECPairFactory(tinysecp);
|
|
54
|
+
|
|
52
55
|
const {kill, nodes} = await spawnLightningCluster({size});
|
|
53
56
|
|
|
54
57
|
const [control, target] = nodes;
|
|
55
58
|
|
|
56
59
|
const {lnd, generate} = control;
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
switch (version) {
|
|
61
|
-
case '0.11.0-beta':
|
|
62
|
-
case '0.11.1-beta':
|
|
63
|
-
// Exit early when funding PSBTs is not supported
|
|
64
|
-
await kill({});
|
|
65
|
-
|
|
66
|
-
return end();
|
|
67
|
-
|
|
68
|
-
default:
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Generate some funds for LND
|
|
73
|
-
await asyncRetry({times}, async () => {
|
|
74
|
-
await addPeer({lnd, public_key: target.id, socket: target.socket});
|
|
61
|
+
try {
|
|
62
|
+
const {version} = await getWalletVersion({lnd});
|
|
75
63
|
|
|
76
|
-
|
|
64
|
+
switch (version) {
|
|
65
|
+
case '0.11.0-beta':
|
|
66
|
+
case '0.11.1-beta':
|
|
67
|
+
// Exit early when funding PSBTs is not supported
|
|
68
|
+
await kill({});
|
|
77
69
|
|
|
78
|
-
|
|
70
|
+
return end();
|
|
79
71
|
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
default:
|
|
73
|
+
break;
|
|
82
74
|
}
|
|
83
|
-
});
|
|
84
75
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
// Generate some funds for LND
|
|
77
|
+
await asyncRetry({times}, async () => {
|
|
78
|
+
await addPeer({lnd, public_key: target.id, socket: target.socket});
|
|
88
79
|
|
|
89
|
-
|
|
80
|
+
await generate({});
|
|
90
81
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
Buffer.from([Transaction.SIGHASH_ALL]),
|
|
275
|
-
]).toString('hex') ,
|
|
276
|
-
hash_type: Transaction.SIGHASH_ALL,
|
|
277
|
-
public_key: controlDerivedKey.public_key,
|
|
82
|
+
const wallet = await getChainBalance({lnd});
|
|
83
|
+
|
|
84
|
+
if (!wallet.chain_balance) {
|
|
85
|
+
throw new Error('ExpectedChainBalanceForNode');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Generate some funds for LND
|
|
90
|
+
await asyncRetry({times}, async () => {
|
|
91
|
+
await target.generate({});
|
|
92
|
+
|
|
93
|
+
const wallet = await getChainBalance({lnd: target.lnd});
|
|
94
|
+
|
|
95
|
+
if (!wallet.chain_balance) {
|
|
96
|
+
throw new Error('ExpectedChainBalanceForNode');
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const {features} = await getWalletInfo({lnd});
|
|
101
|
+
|
|
102
|
+
const isAnchors = !!features.find(n => n.bit === anchorFeatureBit);
|
|
103
|
+
|
|
104
|
+
// Derive a temporary key for control to pay into
|
|
105
|
+
const controlDerivedKey = await getPublicKey({
|
|
106
|
+
lnd,
|
|
107
|
+
family: temporaryFamily,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Derive a temporary key for target to pay into
|
|
111
|
+
const targetDerivedKey = await getPublicKey({
|
|
112
|
+
family: temporaryFamily,
|
|
113
|
+
lnd: target.lnd,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Control should fund and sign a transaction going to the control temp key
|
|
117
|
+
const controlDerivedAddress = payments.p2wpkh({
|
|
118
|
+
network: regtest,
|
|
119
|
+
pubkey: Buffer.from(controlDerivedKey.public_key, 'hex'),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Target should fund and sign a transaction going to the target temp key
|
|
123
|
+
const targetDerivedAddress = payments.p2wpkh({
|
|
124
|
+
network: regtest,
|
|
125
|
+
pubkey: Buffer.from(targetDerivedKey.public_key, 'hex'),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const temporaryKeys = [controlDerivedKey, targetDerivedKey];
|
|
129
|
+
|
|
130
|
+
const giveTokens = ceil(capacity / temporaryKeys.length);
|
|
131
|
+
|
|
132
|
+
// Control can now fund a transaction to pay to the temp address
|
|
133
|
+
const controlFundPsbt = await fundPsbt({
|
|
134
|
+
lnd,
|
|
135
|
+
fee_tokens_per_vbyte: feeRate,
|
|
136
|
+
outputs: [{
|
|
137
|
+
address: controlDerivedAddress.address,
|
|
138
|
+
tokens: giveTokens + ceil(fundingFee / temporaryKeys.length),
|
|
139
|
+
}],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Target can now fund a transaction to pay to the temp address
|
|
143
|
+
const targetFundPsbt = await fundPsbt({
|
|
144
|
+
lnd: target.lnd,
|
|
145
|
+
fee_tokens_per_vbyte: feeRate,
|
|
146
|
+
outputs: [{
|
|
147
|
+
address: targetDerivedAddress.address,
|
|
148
|
+
tokens: giveTokens + ceil(fundingFee / temporaryKeys.length),
|
|
149
|
+
}],
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Control can sign the funding to the temporary address
|
|
153
|
+
const controlSignPsbt = await signPsbt({lnd, psbt: controlFundPsbt.psbt});
|
|
154
|
+
|
|
155
|
+
// Target can sign the funding to the temporary address
|
|
156
|
+
const targetSignPsbt = await signPsbt({
|
|
157
|
+
lnd: target.lnd,
|
|
158
|
+
psbt: targetFundPsbt.psbt,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Decode the control funded PSBT
|
|
162
|
+
const controlPsbt = decodePsbt({ecp, psbt: controlFundPsbt.psbt});
|
|
163
|
+
|
|
164
|
+
// Decode the target funded PSBT
|
|
165
|
+
const targetPsbt = decodePsbt({ecp, psbt: targetFundPsbt.psbt});
|
|
166
|
+
|
|
167
|
+
// Derive the id of the control pre-funding tx
|
|
168
|
+
const controlId = fromHex(controlPsbt.unsigned_transaction).getId();
|
|
169
|
+
|
|
170
|
+
// Derive the id of the target pre-funding tx
|
|
171
|
+
const targetId = fromHex(targetPsbt.unsigned_transaction).getId();
|
|
172
|
+
|
|
173
|
+
// Derive a new control key for a 2:2 multisig
|
|
174
|
+
const controlMultiSigKey = await getPublicKey({family, lnd});
|
|
175
|
+
|
|
176
|
+
// Derive a new target key for a 2:2 multisig
|
|
177
|
+
const targetMultiSigKey = await getPublicKey({family, lnd: target.lnd});
|
|
178
|
+
|
|
179
|
+
const fundingMultiSigKeys = [
|
|
180
|
+
controlMultiSigKey.public_key,
|
|
181
|
+
targetMultiSigKey.public_key,
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
// Make the channel 2:2 funding output from control and target keys
|
|
185
|
+
const dualFundingChannelAddress = payments.p2wsh({
|
|
186
|
+
redeem: p2ms({
|
|
187
|
+
m: fundingMultiSigKeys.length,
|
|
188
|
+
pubkeys: fundingMultiSigKeys.sort().map(n => Buffer.from(n, 'hex')),
|
|
189
|
+
}),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const pendingChannelId = dualFundingChannelAddress.hash;
|
|
193
|
+
|
|
194
|
+
// Create the basic PSBT that spends temporary funds to the 2:2 funding
|
|
195
|
+
const dualFundPsbt = createPsbt({
|
|
196
|
+
outputs: [{
|
|
197
|
+
script: dualFundingChannelAddress.output.toString('hex'),
|
|
198
|
+
tokens: capacity,
|
|
199
|
+
}],
|
|
200
|
+
utxos: [
|
|
201
|
+
{
|
|
202
|
+
id: fromHex(controlSignPsbt.transaction).getId(),
|
|
203
|
+
vout: controlFundPsbt.outputs.findIndex(n => !n.is_change),
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: fromHex(targetSignPsbt.transaction).getId(),
|
|
207
|
+
vout: targetFundPsbt.outputs.findIndex(n => !n.is_change),
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const controlWithoutWitnessTx = fromHex(controlSignPsbt.transaction);
|
|
213
|
+
const targetWithoutWitnessTx = fromHex(targetSignPsbt.transaction);
|
|
214
|
+
|
|
215
|
+
// Eliminate the witnesses
|
|
216
|
+
controlWithoutWitnessTx.ins.forEach((input, index) => {
|
|
217
|
+
return controlWithoutWitnessTx.setWitness(index, []);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
targetWithoutWitnessTx.ins.forEach((input, index) => {
|
|
221
|
+
return targetWithoutWitnessTx.setWitness(index, []);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Add the spending transactions to the psbt
|
|
225
|
+
const psbtWithSpending = updatePsbt({
|
|
226
|
+
ecp,
|
|
227
|
+
psbt: dualFundPsbt.psbt,
|
|
228
|
+
transactions: [
|
|
229
|
+
controlWithoutWitnessTx.toHex(),
|
|
230
|
+
targetWithoutWitnessTx.toHex(),
|
|
231
|
+
],
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const finalFundingPsbt = decodePsbt({ecp, psbt: dualFundPsbt.psbt});
|
|
235
|
+
|
|
236
|
+
const fundingTx = fromHex(finalFundingPsbt.unsigned_transaction);
|
|
237
|
+
|
|
238
|
+
const fundingTxId = fundingTx.getId();
|
|
239
|
+
|
|
240
|
+
const fundingTxVout = fundingTx.outs.findIndex(n => n.value === capacity);
|
|
241
|
+
|
|
242
|
+
const controlTxHash = controlWithoutWitnessTx.getHash();
|
|
243
|
+
|
|
244
|
+
const targetTxHash = targetWithoutWitnessTx.getHash();
|
|
245
|
+
|
|
246
|
+
const controlVin = fundingTx.ins.findIndex(({hash}) => {
|
|
247
|
+
return hash.equals(controlTxHash);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const targetVin = fundingTx.ins.findIndex(({hash}) => {
|
|
251
|
+
return hash.equals(targetTxHash);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const decodePayout = decodePsbt({ecp, psbt: psbtWithSpending.psbt});
|
|
255
|
+
|
|
256
|
+
// Call signTransaction on the unsigned tx that pays from temp -> multisig
|
|
257
|
+
const controlSignDerivedKey = await signTransaction({
|
|
258
|
+
lnd,
|
|
259
|
+
inputs: [{
|
|
260
|
+
key_family: temporaryFamily,
|
|
261
|
+
key_index: controlDerivedKey.index,
|
|
262
|
+
output_script: dualFundingChannelAddress.output.toString('hex'),
|
|
263
|
+
output_tokens: giveTokens + ceil(fundingFee / temporaryKeys.length),
|
|
264
|
+
sighash: Transaction.SIGHASH_ALL,
|
|
278
265
|
vin: controlVin,
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
266
|
+
witness_script: p2pkh({hash: controlDerivedAddress.hash}).output,
|
|
267
|
+
}],
|
|
268
|
+
transaction: decodePayout.unsigned_transaction,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const [controlDerivedSignature] = controlSignDerivedKey.signatures;
|
|
272
|
+
|
|
273
|
+
const controlSignSpendingPsbt = updatePsbt({
|
|
274
|
+
ecp,
|
|
275
|
+
psbt: psbtWithSpending.psbt,
|
|
276
|
+
signatures: controlSignDerivedKey.signatures.map(sig => {
|
|
277
|
+
return {
|
|
278
|
+
signature: Buffer.concat([
|
|
279
|
+
Buffer.from(sig, 'hex'),
|
|
280
|
+
Buffer.from([Transaction.SIGHASH_ALL]),
|
|
281
|
+
]).toString('hex') ,
|
|
282
|
+
hash_type: Transaction.SIGHASH_ALL,
|
|
283
|
+
public_key: controlDerivedKey.public_key,
|
|
284
|
+
vin: controlVin,
|
|
285
|
+
};
|
|
286
|
+
}),
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Call signTransaction on the unsigned tx that pays from temp -> multisig
|
|
290
|
+
const targetSignDerivedKey = await signTransaction({
|
|
291
|
+
inputs: [{
|
|
292
|
+
key_family: temporaryFamily,
|
|
293
|
+
key_index: targetDerivedKey.index,
|
|
294
|
+
output_script: dualFundingChannelAddress.output.toString('hex'),
|
|
295
|
+
output_tokens: giveTokens + ceil(fundingFee / temporaryKeys.length),
|
|
296
|
+
sighash: Transaction.SIGHASH_ALL,
|
|
310
297
|
vin: targetVin,
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
psbts: [controlSignSpendingPsbt, targetSignSpendingPsbt].map(n => n.psbt),
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
// Finalize the combined PSBT
|
|
387
|
-
const finalTempPsbt = finalizePsbt({psbt: combinedTempPsbt.psbt});
|
|
388
|
-
|
|
389
|
-
// Pull out the signed broadcast-ready transaction from the PSBT
|
|
390
|
-
const finalTempTx = extractTransaction({psbt: finalTempPsbt.psbt});
|
|
391
|
-
|
|
392
|
-
// Calculate the size of the tx
|
|
393
|
-
const txSize = fromHex(finalTempTx.transaction).virtualSize();
|
|
394
|
-
|
|
395
|
-
equal(txSize <= fundingFee, true, 'Transaction size is not too large');
|
|
396
|
-
|
|
397
|
-
// Broadcast the transaction to fund the control side
|
|
398
|
-
await broadcastChainTransaction({
|
|
399
|
-
lnd,
|
|
400
|
-
transaction: controlSignPsbt.transaction,
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
// Broadcast the transaction to fund the target side
|
|
404
|
-
await broadcastChainTransaction({
|
|
405
|
-
lnd,
|
|
406
|
-
transaction: targetSignPsbt.transaction,
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
// Broadcast the transaction to fund the channel
|
|
410
|
-
await broadcastChainTransaction({
|
|
411
|
-
lnd,
|
|
412
|
-
transaction: finalTempTx.transaction,
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
// Mine the funding transactions into a block
|
|
416
|
-
await asyncRetry({interval, times}, async () => {
|
|
417
|
-
await control.generate({});
|
|
418
|
-
|
|
419
|
-
const {channels} = await getChannels({lnd});
|
|
420
|
-
|
|
421
|
-
if (!channels.find(n => n.is_active)) {
|
|
422
|
-
throw new Error('ExpectedActiveChannel');
|
|
298
|
+
witness_script: p2pkh({hash: targetDerivedAddress.hash}).output,
|
|
299
|
+
}],
|
|
300
|
+
lnd: target.lnd,
|
|
301
|
+
transaction: decodePsbt({
|
|
302
|
+
ecp,
|
|
303
|
+
psbt: psbtWithSpending.psbt,
|
|
304
|
+
}).unsigned_transaction,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const [targetDerivedSignature] = targetSignDerivedKey.signatures;
|
|
308
|
+
|
|
309
|
+
const targetSignSpendingPsbt = updatePsbt({
|
|
310
|
+
ecp,
|
|
311
|
+
psbt: psbtWithSpending.psbt,
|
|
312
|
+
signatures: targetSignDerivedKey.signatures.map(sig => {
|
|
313
|
+
return {
|
|
314
|
+
signature: Buffer.concat([
|
|
315
|
+
Buffer.from(sig, 'hex'),
|
|
316
|
+
Buffer.from([Transaction.SIGHASH_ALL]),
|
|
317
|
+
]).toString('hex') ,
|
|
318
|
+
hash_type: Transaction.SIGHASH_ALL,
|
|
319
|
+
public_key: targetDerivedKey.public_key,
|
|
320
|
+
vin: targetVin,
|
|
321
|
+
};
|
|
322
|
+
}),
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Use the anticipated funding tx to prepare for a new channel open
|
|
326
|
+
await prepareForChannelProposal({
|
|
327
|
+
cooperative_close_delay: cooperativeCloseDelay,
|
|
328
|
+
id: pendingChannelId.toString('hex'),
|
|
329
|
+
key_index: targetMultiSigKey.index,
|
|
330
|
+
lnd: target.lnd,
|
|
331
|
+
remote_key: controlMultiSigKey.public_key,
|
|
332
|
+
transaction_id: fundingTxId,
|
|
333
|
+
transaction_vout: fundingTxVout,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const coopCloseAddress = await createChainAddress({
|
|
337
|
+
format: 'np2wpkh',
|
|
338
|
+
lnd: control.lnd,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Propose the channel to the target
|
|
342
|
+
await proposeChannel({
|
|
343
|
+
capacity,
|
|
344
|
+
cooperative_close_address: coopCloseAddress.address,
|
|
345
|
+
cooperative_close_delay: cooperativeCloseDelay,
|
|
346
|
+
give_tokens: capacity / fundingMultiSigKeys.length,
|
|
347
|
+
id: pendingChannelId.toString('hex'),
|
|
348
|
+
is_private: true,
|
|
349
|
+
key_index: controlMultiSigKey.index,
|
|
350
|
+
lnd: control.lnd,
|
|
351
|
+
partner_public_key: target.id,
|
|
352
|
+
remote_key: targetMultiSigKey.public_key,
|
|
353
|
+
transaction_id: fundingTxId,
|
|
354
|
+
transaction_vout: fundingTxVout,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const pendingTarget = await getPendingChannels({lnd: target.lnd});
|
|
358
|
+
|
|
359
|
+
const [incoming] = pendingTarget.pending_channels;
|
|
360
|
+
|
|
361
|
+
// LND 0.11.1 and before do not use anchor channels
|
|
362
|
+
if (isAnchors) {
|
|
363
|
+
equal(incoming.remote_balance, 496530, 'Remote balance amount');
|
|
364
|
+
equal(incoming.transaction_fee, 2810, 'Commit tx fee');
|
|
365
|
+
equal(incoming.transaction_weight, 1116, 'Funding tx weight');
|
|
366
|
+
} else {
|
|
367
|
+
equal(incoming.remote_balance, giveTokens - 9050, 'Remote balance');
|
|
368
|
+
equal(incoming.transaction_fee, 9050, 'Commit tx fee');
|
|
369
|
+
equal(incoming.transaction_weight, 724, 'Funding tx weight');
|
|
423
370
|
}
|
|
424
371
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
372
|
+
equal(incoming.capacity, 1000000, 'Incoming capacity is defined');
|
|
373
|
+
equal(incoming.close_transaction_id, undefined, 'Not a closing tx');
|
|
374
|
+
equal(incoming.is_active, false, 'Not active yet');
|
|
375
|
+
equal(incoming.is_closing, false, 'Channel is not closing');
|
|
376
|
+
equal(incoming.is_opening, true, 'Channel is opening');
|
|
377
|
+
equal(incoming.is_partner_initiated, true, 'Peer initiated the channel');
|
|
378
|
+
equal(incoming.local_balance, giveTokens, 'The incoming channel is split');
|
|
379
|
+
equal(incoming.local_reserve, capacity * reserveRatio, 'Reserve ratio');
|
|
380
|
+
equal(incoming.partner_public_key, control.id, 'Peer key');
|
|
381
|
+
equal(incoming.pending_balance, undefined, 'No tokens pending');
|
|
382
|
+
equal(incoming.pending_payments, undefined, 'No HTLCs active');
|
|
383
|
+
equal(incoming.received, 0, 'Nothing received');
|
|
384
|
+
equal(incoming.recovered_tokens, undefined, 'No recovery');
|
|
385
|
+
equal(incoming.remote_reserve, capacity * reserveRatio, 'Got reserve');
|
|
386
|
+
equal(incoming.sent, 0, 'Nothing sent');
|
|
387
|
+
equal(incoming.timelock_expiration, undefined, 'No timelock');
|
|
388
|
+
equal(incoming.transaction_id, fundingTxId, 'Funding tx id is correct');
|
|
389
|
+
equal(incoming.transaction_vout, fundingTxVout, 'Funding vout is correct');
|
|
390
|
+
|
|
391
|
+
// Setup the combined signed PSBTs that fund the channel
|
|
392
|
+
const combinedTempPsbt = combinePsbts({
|
|
393
|
+
ecp,
|
|
394
|
+
psbts: [
|
|
395
|
+
controlSignSpendingPsbt,
|
|
396
|
+
targetSignSpendingPsbt,
|
|
397
|
+
].map(n => n.psbt),
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Finalize the combined PSBT
|
|
401
|
+
const finalTempPsbt = finalizePsbt({ecp, psbt: combinedTempPsbt.psbt});
|
|
402
|
+
|
|
403
|
+
// Pull out the signed broadcast-ready transaction from the PSBT
|
|
404
|
+
const finalTempTx = extractTransaction({ecp, psbt: finalTempPsbt.psbt});
|
|
405
|
+
|
|
406
|
+
// Calculate the size of the tx
|
|
407
|
+
const txSize = fromHex(finalTempTx.transaction).virtualSize();
|
|
408
|
+
|
|
409
|
+
equal(txSize <= fundingFee, true, 'Transaction size is not too large');
|
|
410
|
+
|
|
411
|
+
// Broadcast the transaction to fund the control side
|
|
412
|
+
await broadcastChainTransaction({
|
|
413
|
+
lnd,
|
|
414
|
+
transaction: controlSignPsbt.transaction,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Broadcast the transaction to fund the target side
|
|
418
|
+
await broadcastChainTransaction({
|
|
419
|
+
lnd,
|
|
420
|
+
transaction: targetSignPsbt.transaction,
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// Broadcast the transaction to fund the channel
|
|
424
|
+
await broadcastChainTransaction({
|
|
425
|
+
lnd,
|
|
426
|
+
transaction: finalTempTx.transaction,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// Mine the funding transactions into a block
|
|
430
|
+
await asyncRetry({interval, times}, async () => {
|
|
431
|
+
await control.generate({});
|
|
432
|
+
|
|
433
|
+
const {channels} = await getChannels({lnd});
|
|
434
|
+
|
|
435
|
+
if (!channels.find(n => n.is_active)) {
|
|
436
|
+
throw new Error('ExpectedActiveChannel');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return;
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const controlChannels = await getChannels({lnd});
|
|
443
|
+
|
|
444
|
+
const [controlChannel] = controlChannels.channels;
|
|
445
|
+
|
|
446
|
+
const closeAddr = coopCloseAddress.address;
|
|
447
|
+
|
|
448
|
+
// LND 0.11.1 and before do not use anchor channels
|
|
449
|
+
if (isAnchors) {
|
|
450
|
+
equal(controlChannel.commit_transaction_fee, 2810, 'Regular tx fee');
|
|
451
|
+
equal(controlChannel.commit_transaction_weight, 1116, 'Regular tx size');
|
|
452
|
+
} else {
|
|
453
|
+
equal(controlChannel.commit_transaction_fee, 9050, 'Regular tx fee');
|
|
454
|
+
equal(controlChannel.commit_transaction_weight, 724, 'Regular tx size');
|
|
455
|
+
}
|
|
433
456
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
equal(controlChannel.
|
|
437
|
-
equal(controlChannel.
|
|
438
|
-
|
|
439
|
-
equal(controlChannel.
|
|
440
|
-
equal(controlChannel.
|
|
441
|
-
|
|
457
|
+
equal(controlChannel.capacity, capacity, 'Channel with capacity created');
|
|
458
|
+
equal(controlChannel.cooperative_close_address, closeAddr, 'Got addr');
|
|
459
|
+
equal(!!controlChannel.cooperative_close_delay_height, true, 'Thaw');
|
|
460
|
+
equal(!!controlChannel.id, true, 'Got channel id');
|
|
461
|
+
equal(controlChannel.is_active, true, 'Channel is active and ready');
|
|
462
|
+
equal(controlChannel.is_closing, false, 'Channel is not closing');
|
|
463
|
+
equal(controlChannel.is_opening, false, 'Channel is already opened');
|
|
464
|
+
equal(controlChannel.is_partner_initiated, false, 'Control opened');
|
|
465
|
+
equal(controlChannel.is_private, true, 'Channel is private');
|
|
466
|
+
equal(controlChannel.local_balance, incoming.remote_balance, 'Control');
|
|
467
|
+
equal(controlChannel.local_csv, 144, 'Channel CSV');
|
|
468
|
+
ok(controlChannel.local_dust >= 354, 'Channel dust');
|
|
469
|
+
equal(controlChannel.local_given, giveTokens, 'Channel tokens given over');
|
|
470
|
+
equal(controlChannel.local_max_htlcs, 483, 'Channel HTLCs max set');
|
|
471
|
+
equal(controlChannel.partner_public_key, target.id, 'R-key');
|
|
472
|
+
equal(controlChannel.transaction_id, fundingTxId, 'Funding tx id');
|
|
473
|
+
equal(controlChannel.transaction_vout, fundingTxVout, 'Funding tx vout');
|
|
474
|
+
|
|
475
|
+
const targetChannels = await getChannels({lnd: target.lnd});
|
|
476
|
+
|
|
477
|
+
const [targetChannel] = targetChannels.channels;
|
|
478
|
+
|
|
479
|
+
// LND 0.11.1 and before do not use anchor channels
|
|
480
|
+
if (isAnchors) {
|
|
481
|
+
equal(targetChannel.commit_transaction_fee, 2810, 'Regular tx fee');
|
|
482
|
+
equal(targetChannel.commit_transaction_weight, 1116, 'Regular tx size');
|
|
483
|
+
} else {
|
|
484
|
+
equal(targetChannel.commit_transaction_fee, 9050, 'Regular tx fee');
|
|
485
|
+
equal(targetChannel.commit_transaction_weight, 724, 'Regular tx size');
|
|
486
|
+
}
|
|
442
487
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
// LND 0.11.1 and before do not use anchor channels
|
|
466
|
-
if (isAnchors) {
|
|
467
|
-
equal(targetChannel.commit_transaction_fee, 2810, 'Regular tx commit fee');
|
|
468
|
-
equal(targetChannel.commit_transaction_weight, 1116, 'Regular tx size');
|
|
469
|
-
} else {
|
|
470
|
-
equal(targetChannel.commit_transaction_fee, 9050, 'Regular tx commit fee');
|
|
471
|
-
equal(targetChannel.commit_transaction_weight, 724, 'Regular tx size');
|
|
488
|
+
equal(targetChannel.capacity, capacity, 'Channel with capacity created');
|
|
489
|
+
equal(targetChannel.cooperative_close_address, undefined, 'No close addr');
|
|
490
|
+
equal(!!targetChannel.cooperative_close_delay_height, true, 'Thaw height');
|
|
491
|
+
equal(!!targetChannel.id, true, 'Got channel id');
|
|
492
|
+
equal(targetChannel.is_active, true, 'Channel is active and ready');
|
|
493
|
+
equal(targetChannel.is_closing, false, 'Channel is not closing');
|
|
494
|
+
equal(targetChannel.is_opening, false, 'Channel is already opened');
|
|
495
|
+
equal(targetChannel.is_partner_initiated, true, 'Control opened');
|
|
496
|
+
equal(targetChannel.is_private, true, 'Channel is private');
|
|
497
|
+
equal(targetChannel.local_balance, giveTokens, 'Target tokens');
|
|
498
|
+
equal(targetChannel.local_csv, 144, 'Channel CSV');
|
|
499
|
+
ok(targetChannel.local_dust >= 354, 'Channel dust');
|
|
500
|
+
equal(targetChannel.local_given, 0, 'No tokens given');
|
|
501
|
+
equal(targetChannel.local_max_htlcs, 483, 'Channel HTLCs max set');
|
|
502
|
+
equal(targetChannel.partner_public_key, control.id, 'R-key');
|
|
503
|
+
equal(targetChannel.transaction_id, fundingTxId, 'Funding tx id');
|
|
504
|
+
equal(targetChannel.transaction_vout, fundingTxVout, 'Funding tx vout');
|
|
505
|
+
} catch (err) {
|
|
506
|
+
equal(err, null, 'Expected no error');
|
|
507
|
+
} finally {
|
|
508
|
+
await kill({});
|
|
472
509
|
}
|
|
473
510
|
|
|
474
|
-
equal(targetChannel.capacity, capacity, 'Channel with capacity created');
|
|
475
|
-
equal(targetChannel.cooperative_close_address, undefined, 'No close addr');
|
|
476
|
-
equal(!!targetChannel.cooperative_close_delay_height, true, 'Thaw height');
|
|
477
|
-
equal(!!targetChannel.id, true, 'Got channel id');
|
|
478
|
-
equal(targetChannel.is_active, true, 'Channel is active and ready');
|
|
479
|
-
equal(targetChannel.is_closing, false, 'Channel is not closing');
|
|
480
|
-
equal(targetChannel.is_opening, false, 'Channel is already opened');
|
|
481
|
-
equal(targetChannel.is_partner_initiated, true, 'Control opened');
|
|
482
|
-
equal(targetChannel.is_private, true, 'Channel is private');
|
|
483
|
-
equal(targetChannel.local_balance, giveTokens, 'Target tokens');
|
|
484
|
-
equal(targetChannel.local_csv, 144, 'Channel CSV');
|
|
485
|
-
ok(targetChannel.local_dust >= 354, 'Channel dust');
|
|
486
|
-
equal(targetChannel.local_given, 0, 'No tokens given');
|
|
487
|
-
equal(targetChannel.local_max_htlcs, 483, 'Channel HTLCs max set');
|
|
488
|
-
equal(targetChannel.partner_public_key, control.id, 'R-key');
|
|
489
|
-
equal(targetChannel.transaction_id, fundingTxId, 'Funding tx id');
|
|
490
|
-
equal(targetChannel.transaction_vout, fundingTxVout, 'Funding tx vout');
|
|
491
|
-
|
|
492
|
-
await kill({});
|
|
493
|
-
|
|
494
511
|
return end();
|
|
495
512
|
});
|