ln-service 53.5.2 → 53.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Versions
2
2
 
3
+ ## 53.6.0
4
+
5
+ - `partiallySignPsbt`: Add method to add a partial signature to a PSBT
6
+
3
7
  ## 53.5.2
4
8
 
5
9
  - `getPayments`: Correct paging issue that prevented paging through all results
package/README.md CHANGED
@@ -187,6 +187,7 @@ for `unlocker` methods.
187
187
  - [openChannel](#openchannel) - Open a new channel
188
188
  - [openChannels](#openchannels) - Open channels with external funding
189
189
  - [parsePaymentRequest](#parsepaymentrequest) - Parse a BOLT11 Payment Request
190
+ - [partiallySignPsbt](#partiallysignpsbt) - Sign a PSBT without finalizing it
190
191
  - [pay](#pay) - Send a payment
191
192
  - [payViaPaymentDetails](#payviapaymentdetails) - Pay using decomposed details
192
193
  - [payViaPaymentRequest](#payviapaymentrequest) - Pay using a payment request
@@ -3392,6 +3393,46 @@ const {parsePaymentRequest} = require('ln-service');
3392
3393
  const requestDetails = parsePaymentRequest({request: 'paymentRequestString'});
3393
3394
  ```
3394
3395
 
3396
+ ### partiallySignPsbt
3397
+
3398
+ Sign a PSBT to produce a partially signed PSBT
3399
+
3400
+ Requires `onchain:write` permission
3401
+
3402
+ Requires LND built with `walletrpc` tag
3403
+
3404
+ This method is not supported in LND 0.14.1 and below
3405
+
3406
+ {
3407
+ lnd: <Authenticated LND API Object>
3408
+ psbt: <Funded PSBT Hex String>
3409
+ }
3410
+
3411
+ @returns via cbk or Promise
3412
+ {
3413
+ psbt: <Partially Signed PSBT Hex String>
3414
+ }
3415
+
3416
+
3417
+ Example:
3418
+
3419
+ ```node
3420
+ const {broadcastChainTransaction} = require('ln-service');
3421
+ const {fundPsbt, partiallySignPsbt} = require('ln-service');
3422
+ const {extractTransaction, finalizePsbt} = require('psbt');
3423
+
3424
+ const funding = await fundPsbt({
3425
+ lnd,
3426
+ outputs: [{address: chainAddress, tokens: 100000}]
3427
+ })
3428
+
3429
+ const finalize = finalizePsbt({psbt: funding.psbt});
3430
+
3431
+ const {transaction} = extractTransaction({psbt: finalize.psbt});
3432
+
3433
+ await broadcastChainTransaction({lnd, transaction});
3434
+ ```
3435
+
3395
3436
  ### pay
3396
3437
 
3397
3438
  Make a payment.
package/index.js CHANGED
@@ -76,6 +76,7 @@ const {lockUtxo} = require('lightning');
76
76
  const {openChannel} = require('lightning');
77
77
  const {openChannels} = require('lightning');
78
78
  const {parsePaymentRequest} = require('invoices');
79
+ const {partiallySignPsbt} = require('lightning');
79
80
  const {pay} = require('lightning');
80
81
  const {payViaPaymentDetails} = require('lightning');
81
82
  const {payViaPaymentRequest} = require('lightning');
@@ -215,6 +216,7 @@ module.exports = {
215
216
  openChannel,
216
217
  openChannels,
217
218
  parsePaymentRequest,
219
+ partiallySignPsbt,
218
220
  pay,
219
221
  payViaPaymentDetails,
220
222
  payViaPaymentRequest,
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "cors": "2.8.5",
12
12
  "express": "4.17.2",
13
13
  "invoices": "2.0.3",
14
- "lightning": "5.4.0",
14
+ "lightning": "5.4.1",
15
15
  "macaroon": "3.0.4",
16
16
  "morgan": "1.10.0",
17
17
  "ws": "8.4.2"
@@ -26,9 +26,9 @@
26
26
  "bitcoinjs-lib": "6.0.1",
27
27
  "bn.js": "5.2.0",
28
28
  "ecpair": "2.0.1",
29
- "ln-docker-daemons": "2.2.0",
29
+ "ln-docker-daemons": "2.2.1",
30
30
  "portfinder": "1.0.28",
31
- "psbt": "1.1.10",
31
+ "psbt": "1.1.11",
32
32
  "rimraf": "3.0.2",
33
33
  "secp256k1": "4.0.3",
34
34
  "tiny-secp256k1": "2.2.0",
@@ -53,7 +53,17 @@
53
53
  "url": "https://github.com/alexbosworth/ln-service.git"
54
54
  },
55
55
  "scripts": {
56
+ "integration-test-daily-lnd-build": "DOCKER_LND_VERSION=daily-testing-only npm run test",
57
+ "integration-test-0.14.1": "DOCKER_LND_VERSION=v0.14.1-beta npm run test",
58
+ "integration-test-0.14.0": "DOCKER_LND_VERSION=v0.14.0-beta npm run test",
59
+ "integration-test-0.13.4": "DOCKER_LND_VERSION=v0.13.4-beta npm run test",
60
+ "integration-test-0.13.3": "DOCKER_LND_VERSION=v0.13.3-beta npm run test",
61
+ "integration-test-0.13.2": "DOCKER_LND_VERSION=v0.13.2-beta npm run test",
62
+ "integration-test-0.13.1": "DOCKER_LND_VERSION=v0.13.1-beta npm run test",
63
+ "integration-test-0.13.0": "DOCKER_LND_VERSION=v0.13.0-beta npm run test",
64
+ "integration-test-0.12.1": "DOCKER_LND_VERSION=v0.12.1-beta npm run test",
65
+ "integration-test-0.12.0": "DOCKER_LND_VERSION=v0.12.0-beta npm run test",
56
66
  "test": "echo $DOCKER_LND_VERSION && tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 200 test/autopilotrpc-integration/*.js test/chainrpc-integration/*.js test/integration/*.js test/invoicesrpc-integration/*.js test/routerrpc-integration/*.js test/signerrpc-integration/*.js test/tower_clientrpc-integration/*.js test/tower_serverrpc-integration/*.js test/walletrpc-integration/*.js"
57
67
  },
58
- "version": "53.5.2"
68
+ "version": "53.6.0"
59
69
  }
@@ -31,7 +31,7 @@ const timeout = 1000 * 5;
31
31
  const times = 200;
32
32
 
33
33
  // Forfeiting a pending channel should remove the pending channel
34
- test(`Forfeit pending channel`, async ({end, equal}) => {
34
+ test(`Forfeit pending channel`, async ({end, equal, strictSame}) => {
35
35
  const {kill, nodes} = await spawnLightningCluster({size});
36
36
 
37
37
  const [control, target, remote] = nodes;
@@ -136,16 +136,20 @@ test(`Forfeit pending channel`, async ({end, equal}) => {
136
136
  equal(code, 503, 'Pending channel cannot be canceled');
137
137
  }
138
138
 
139
- await deletePendingChannel({
140
- lnd,
141
- confirmed_transaction: transaction,
142
- pending_transaction: stuckTx.transaction,
143
- pending_transaction_vout: pending.transaction_vout,
144
- });
139
+ try {
140
+ await deletePendingChannel({
141
+ lnd,
142
+ confirmed_transaction: transaction,
143
+ pending_transaction: stuckTx.transaction,
144
+ pending_transaction_vout: pending.transaction_vout,
145
+ });
145
146
 
146
- const [stillPending] = (await getPendingChannels({lnd})).pending_channels;
147
+ const [notPending] = (await getPendingChannels({lnd})).pending_channels;
147
148
 
148
- equal(stillPending, undefined, 'Conflicting pending channel deleted');
149
+ equal(notPending, undefined, 'Conflicting pending channel deleted');
150
+ } catch (err) {
151
+ strictSame(err, [501, 'DeletePendingChannelMethodNotSupported']);
152
+ }
149
153
  } catch (err) {
150
154
  equal(err, null, 'No error is expected');
151
155
  } finally {
@@ -177,7 +177,14 @@ test(`Get pending channels`, async ({end, equal}) => {
177
177
  equal(forceClose.partner_public_key, target.id, 'pk');
178
178
  equal(forceClose.received, 0, 'No receive amount');
179
179
  equal(forceClose.recovered_tokens, undefined, 'No recovered amount');
180
- equal(forceClose.remote_balance, 0, 'No remote balance');
180
+
181
+ // LND 0.14.1 and below do not support remote balance info
182
+ if (!!forceClose.remote_balance) {
183
+ equal(forceClose.remote_balance, giftTokens, 'Got gift remote balance');
184
+ } else {
185
+ equal(forceClose.remote_balance, 0, 'No remote balance');
186
+ }
187
+
181
188
  equal(forceClose.sent, 0, 'No sent amount');
182
189
  equal(!!forceClose.timelock_blocks, true, 'Timelock blocks set');
183
190
  equal(forceClose.timelock_expiration, startHeight + 265, 'Funds timelock');
@@ -0,0 +1,209 @@
1
+ const asyncEach = require('async/each');
2
+ const asyncMap = require('async/map');
3
+ const asyncRetry = require('async/retry');
4
+ const {combinePsbts} = require('psbt');
5
+ const {createPsbt} = require('psbt');
6
+ const {decodePsbt} = require('psbt');
7
+ const {extractTransaction} = require('psbt');
8
+ const {finalizePsbt} = require('psbt');
9
+ const {spawnLightningCluster} = require('ln-docker-daemons');
10
+ const {test} = require('@alexbosworth/tap');
11
+ const {Transaction} = require('bitcoinjs-lib');
12
+ const {updatePsbt} = require('psbt');
13
+
14
+ const {broadcastChainTransaction} = require('./../../');
15
+ const {createChainAddress} = require('./../../');
16
+ const {fundPsbt} = require('./../../');
17
+ const {getChainBalance} = require('./../../');
18
+ const {getChainTransactions} = require('./../../');
19
+ const {getUtxos} = require('./../../');
20
+ const {partiallySignPsbt} = require('./../../');
21
+ const {sendToChainAddresses} = require('./../../');
22
+
23
+ const count = 150;
24
+ const flatten = arr => [].concat(...arr);
25
+ const interval = 10;
26
+ const size = 3;
27
+ const startingFunds = 1e7;
28
+ const times = 1000;
29
+ const tokens = 1e6;
30
+
31
+ // Partially signing a PSBT should result in a partially signed PSBT
32
+ test(`Partially sign PSBT`, async ({end, equal, strictSame}) => {
33
+ const {kill, nodes} = await spawnLightningCluster({size});
34
+
35
+ const [control, target, remote] = nodes;
36
+
37
+ try {
38
+ // Generate some funds for the control
39
+ await control.generate({count});
40
+
41
+ // Send starting coins to target and remote
42
+ await sendToChainAddresses({
43
+ lnd: control.lnd,
44
+ send_to: await asyncMap([target, remote], async ({lnd}) => {
45
+ return {
46
+ address: (await createChainAddress({lnd})).address,
47
+ tokens: startingFunds,
48
+ };
49
+ }),
50
+ });
51
+
52
+ // Make sure that the nodes have funds
53
+ await asyncRetry({interval, times}, async () => {
54
+ await asyncEach([control, target, remote], async ({lnd}) => {
55
+ await control.generate({});
56
+
57
+ if (!(await getChainBalance({lnd})).chain_balance) {
58
+ throw new Error('WaitingForChainBalance');
59
+ }
60
+ });
61
+ });
62
+
63
+ // Get UTXOs to spend
64
+ const [controlUtxo] = (await getUtxos({lnd: control.lnd})).utxos.reverse();
65
+ const [targetUtxo] = (await getUtxos({lnd: target.lnd})).utxos;
66
+ const [remoteUtxo] = (await getUtxos({lnd: remote.lnd})).utxos;
67
+
68
+ // Make addresses to spend to
69
+ const addresses = await asyncMap(nodes, async ({lnd}) => {
70
+ return await createChainAddress({lnd});
71
+ });
72
+
73
+ const [controlAddress, targetAddress, remoteAddress] = addresses;
74
+
75
+ // Fund the addresses using the selected UTXOs
76
+ const controlFund = await fundPsbt({
77
+ inputs: [{
78
+ transaction_id: controlUtxo.transaction_id,
79
+ transaction_vout: controlUtxo.transaction_vout,
80
+ }],
81
+ lnd: control.lnd,
82
+ outputs: [{tokens, address: controlAddress.address}],
83
+ });
84
+
85
+ const targetFund = await fundPsbt({
86
+ inputs: [{
87
+ transaction_id: targetUtxo.transaction_id,
88
+ transaction_vout: targetUtxo.transaction_vout,
89
+ }],
90
+ lnd: target.lnd,
91
+ outputs: [{tokens, address: targetAddress.address}],
92
+ });
93
+
94
+ const remoteFund = await fundPsbt({
95
+ inputs: [{
96
+ transaction_id: remoteUtxo.transaction_id,
97
+ transaction_vout: remoteUtxo.transaction_vout,
98
+ }],
99
+ lnd: remote.lnd,
100
+ outputs: [{tokens, address: remoteAddress.address}],
101
+ });
102
+
103
+ // Collect all inputs that are spending
104
+ const inputs = []
105
+ .concat(controlFund.inputs)
106
+ .concat(targetFund.inputs)
107
+ .concat(remoteFund.inputs);
108
+
109
+ // Collect all outputs being sent to
110
+ const outputs = []
111
+ .concat(controlFund.outputs)
112
+ .concat(targetFund.outputs)
113
+ .concat(remoteFund.outputs);
114
+
115
+ // Make a new PSBT that includes all the outputs and inputs
116
+ const base = createPsbt({
117
+ outputs: outputs.map(output => ({
118
+ script: output.output_script,
119
+ tokens: output.tokens,
120
+ })),
121
+ utxos: inputs.map(input => ({
122
+ id: input.transaction_id,
123
+ vout: input.transaction_vout,
124
+ })),
125
+ });
126
+
127
+ // Decode the PSBTs to get signing key details
128
+ const controlDecoded = decodePsbt({psbt: controlFund.psbt});
129
+ const targetDecoded = decodePsbt({psbt: targetFund.psbt});
130
+ const remoteDecoded = decodePsbt({psbt: remoteFund.psbt});
131
+
132
+ // Collect all the BIP32 derivations
133
+ const controlBip32Derivations = controlDecoded.inputs
134
+ .map(n => n.bip32_derivations);
135
+
136
+ const targetBip32Derivations = targetDecoded.inputs
137
+ .map(n => n.bip32_derivations);
138
+
139
+ const remoteBip32Derivations = remoteDecoded.inputs
140
+ .map(n => n.bip32_derivations);
141
+
142
+ const controlTransactions = controlDecoded.inputs
143
+ .map(n => n.non_witness_utxo);
144
+
145
+ const targetTransactions = targetDecoded.inputs
146
+ .map(n => n.non_witness_utxo);
147
+
148
+ const remoteTransactions = remoteDecoded.inputs
149
+ .map(n => n.non_witness_utxo);
150
+
151
+ const allDerivations = []
152
+ .concat(controlBip32Derivations)
153
+ .concat(targetBip32Derivations)
154
+ .concat(remoteBip32Derivations);
155
+
156
+ const bip32Derivations = flatten(allDerivations);
157
+
158
+ // Update the PSBT so that it has the consolidated details
159
+ const updated = updatePsbt({
160
+ bip32_derivations: bip32Derivations,
161
+ psbt: base.psbt,
162
+ sighashes: inputs.map(input => ({
163
+ id: input.transaction_id,
164
+ sighash: Transaction.SIGHASH_ALL,
165
+ vout: input.transaction_vout,
166
+ })),
167
+ transactions: controlTransactions.concat(targetTransactions),
168
+ });
169
+
170
+ await partiallySignPsbt({lnd: control.lnd, psbt: updated.psbt});
171
+
172
+ // Sign the PSBT
173
+ const psbts = await asyncMap(nodes, async ({lnd}) => {
174
+ return (await partiallySignPsbt({lnd, psbt: updated.psbt})).psbt;
175
+ });
176
+
177
+ // Finalize the signatures
178
+ const finalize = finalizePsbt({psbt: combinePsbts({psbts}).psbt});
179
+
180
+ // Pull out the transaction
181
+ const {transaction} = extractTransaction({psbt: finalize.psbt});
182
+
183
+ // Publish the transaction
184
+ await broadcastChainTransaction({lnd: control.lnd, transaction});
185
+
186
+ // Make sure that the transaction confirms into a block
187
+ await asyncEach(nodes, async ({lnd}) => {
188
+ return asyncRetry({interval, times}, async () => {
189
+ await control.generate({});
190
+
191
+ const {utxos} = await getUtxos({lnd});
192
+
193
+ const [confirmed] = utxos.filter(n => n.tokens === tokens);
194
+
195
+ if (!confirmed || !confirmed.confirmation_count) {
196
+ throw new Error('ExpectedConfirmedCoin');
197
+ }
198
+
199
+ equal(confirmed.tokens, tokens, 'Got confirmed funds');
200
+ });
201
+ });
202
+ } catch (err) {
203
+ strictSame(err, [501, 'PartiallySignPsbtMethodNotSupported'], 'NoSupport');
204
+ } finally {
205
+ await kill({});
206
+ }
207
+
208
+ return end();
209
+ });