ln-service 53.4.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,17 @@
1
1
  # Versions
2
2
 
3
+ ## 53.6.0
4
+
5
+ - `partiallySignPsbt`: Add method to add a partial signature to a PSBT
6
+
7
+ ## 53.5.2
8
+
9
+ - `getPayments`: Correct paging issue that prevented paging through all results
10
+
11
+ ## 53.5.0
12
+
13
+ - `createWallet`: Add support for returning the admin `macaroon`
14
+
3
15
  ## 53.4.2
4
16
 
5
17
  - `pay`, `payViaPaymentRequest`: Fix support for `outgoing_channels` constraint
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
@@ -762,6 +763,9 @@ Requires unlocked lnd and unauthenticated LND API Object
762
763
  }
763
764
 
764
765
  @returns via cbk or Promise
766
+ {
767
+ macaroon: <Base64 Encoded Admin Macaroon String>
768
+ }
765
769
 
766
770
  Example:
767
771
 
@@ -3389,6 +3393,46 @@ const {parsePaymentRequest} = require('ln-service');
3389
3393
  const requestDetails = parsePaymentRequest({request: 'paymentRequestString'});
3390
3394
  ```
3391
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
+
3392
3436
  ### pay
3393
3437
 
3394
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.3.2",
14
+ "lightning": "5.4.1",
15
15
  "macaroon": "3.0.4",
16
16
  "morgan": "1.10.0",
17
17
  "ws": "8.4.2"
@@ -26,12 +26,12 @@
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
- "tiny-secp256k1": "2.1.2",
34
+ "tiny-secp256k1": "2.2.0",
35
35
  "uuid": "8.3.2",
36
36
  "varuint-bitcoin": "1.1.2"
37
37
  },
@@ -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.4.2"
68
+ "version": "53.6.0"
59
69
  }
@@ -8,6 +8,7 @@ const {test} = require('@alexbosworth/tap');
8
8
 
9
9
  const {addPeer} = require('./../../');
10
10
  const {broadcastChainTransaction} = require('./../../');
11
+ const {cancelPendingChannel} = require('./../../');
11
12
  const {createCluster} = require('./../macros');
12
13
  const {delay} = require('./../macros');
13
14
  const {deletePendingChannel} = require('./../../');
@@ -30,7 +31,7 @@ const timeout = 1000 * 5;
30
31
  const times = 200;
31
32
 
32
33
  // Forfeiting a pending channel should remove the pending channel
33
- test(`Forfeit pending channel`, async ({end, equal}) => {
34
+ test(`Forfeit pending channel`, async ({end, equal, strictSame}) => {
34
35
  const {kill, nodes} = await spawnLightningCluster({size});
35
36
 
36
37
  const [control, target, remote] = nodes;
@@ -60,7 +61,7 @@ test(`Forfeit pending channel`, async ({end, equal}) => {
60
61
  // Sign the funding to the target
61
62
  const signTarget = await signPsbt({lnd, psbt: fundTarget.psbt});
62
63
 
63
- // Fund the target channel
64
+ // Fund the target channel that will get stuck
64
65
  await fundPendingChannels({
65
66
  lnd,
66
67
  channels: proposeToTarget.pending.map(n => n.id),
@@ -124,16 +125,31 @@ test(`Forfeit pending channel`, async ({end, equal}) => {
124
125
 
125
126
  const stuckTx = extractTransaction({psbt: signTarget.psbt});
126
127
 
127
- await deletePendingChannel({
128
- lnd,
129
- confirmed_transaction: transaction,
130
- pending_transaction: stuckTx.transaction,
131
- pending_transaction_vout: pending.transaction_vout,
132
- });
128
+ const [stuckPending] = proposeToTarget.pending;
129
+
130
+ // Try to cancel the pending channel, it will fail
131
+ try {
132
+ await cancelPendingChannel({lnd, id: stuckPending.id});
133
+ } catch (err) {
134
+ const [code] = err;
135
+
136
+ equal(code, 503, 'Pending channel cannot be canceled');
137
+ }
138
+
139
+ try {
140
+ await deletePendingChannel({
141
+ lnd,
142
+ confirmed_transaction: transaction,
143
+ pending_transaction: stuckTx.transaction,
144
+ pending_transaction_vout: pending.transaction_vout,
145
+ });
133
146
 
134
- const [stillPending] = (await getPendingChannels({lnd})).pending_channels;
147
+ const [notPending] = (await getPendingChannels({lnd})).pending_channels;
135
148
 
136
- equal(stillPending, undefined, 'Conflicting pending channel deleted');
149
+ equal(notPending, undefined, 'Conflicting pending channel deleted');
150
+ } catch (err) {
151
+ strictSame(err, [501, 'DeletePendingChannelMethodNotSupported']);
152
+ }
137
153
  } catch (err) {
138
154
  equal(err, null, 'No error is expected');
139
155
  } finally {
@@ -9,7 +9,7 @@ const {setupChannel} = require('./../macros');
9
9
 
10
10
  const interval = 100;
11
11
  const size = 3;
12
- const times = 300;
12
+ const times = 800;
13
13
 
14
14
  // Getting the network centrality should return the centrality scores
15
15
  test(`Get network centrality`, async ({end, equal, strictSame}) => {
@@ -19,42 +19,50 @@ test(`Get network centrality`, async ({end, equal, strictSame}) => {
19
19
 
20
20
  const {lnd} = control;
21
21
 
22
- await setupChannel({lnd, generate: control.generate, to: target});
22
+ try {
23
+ await control.generate({count: 100});
23
24
 
24
- await setupChannel({
25
- generate: target.generate,
26
- lnd: target.lnd,
27
- to: remote,
28
- });
29
-
30
- await asyncRetry({interval, times}, async () => {
31
25
  await addPeer({lnd, public_key: remote.id, socket: remote.socket});
32
26
 
33
- const {nodes} = await getNetworkCentrality({lnd});
34
-
35
- const controlScore = nodes.find(n => n.public_key === control.id);
36
- const remoteScore = nodes.find(n => n.public_key === remote.id);
37
- const targetScore = nodes.find(n => n.public_key === target.id);
38
-
39
- if (!targetScore.betweenness || !targetScore.betweenness_normalized) {
40
- throw new Error('UnexpectedValueForTargetScoreBetweenness');
41
- }
42
-
43
- if (targetScore.betweenness !== 1e6) {
44
- throw new Error('WrongBetweennessScore');
45
- }
46
-
47
- equal(controlScore.betweenness, 0, 'No centrality on control');
48
- equal(controlScore.betweenness_normalized, 0, 'No centrality on control');
49
- equal(remoteScore.betweenness, 0, 'No centrality on remote');
50
- equal(remoteScore.betweenness_normalized, 0, 'No centrality on remote');
51
- equal(targetScore.betweenness, 1e6, 'Centrality around target');
52
- equal(targetScore.betweenness_normalized, 1e6, 'Centrality around target');
53
-
54
- return;
55
- });
56
-
57
- await kill({});
27
+ await setupChannel({lnd, generate: control.generate, to: target});
28
+
29
+ await setupChannel({
30
+ generate: target.generate,
31
+ lnd: target.lnd,
32
+ to: remote,
33
+ });
34
+
35
+ await asyncRetry({interval, times}, async () => {
36
+ await addPeer({lnd, public_key: remote.id, socket: remote.socket});
37
+
38
+ const {nodes} = await getNetworkCentrality({lnd});
39
+
40
+ const controlScore = nodes.find(n => n.public_key === control.id);
41
+ const remoteScore = nodes.find(n => n.public_key === remote.id);
42
+ const targetScore = nodes.find(n => n.public_key === target.id);
43
+
44
+ if (!targetScore.betweenness || !targetScore.betweenness_normalized) {
45
+ throw new Error('UnexpectedValueForTargetScoreBetweenness');
46
+ }
47
+
48
+ if (targetScore.betweenness !== 1e6) {
49
+ throw new Error('WrongBetweennessScore');
50
+ }
51
+
52
+ equal(controlScore.betweenness, 0, 'No centrality on control');
53
+ equal(controlScore.betweenness_normalized, 0, 'No control centrality');
54
+ equal(remoteScore.betweenness, 0, 'No centrality on remote');
55
+ equal(remoteScore.betweenness_normalized, 0, 'No centrality on remote');
56
+ equal(targetScore.betweenness, 1e6, 'Centrality around target');
57
+ equal(targetScore.betweenness_normalized, 1e6, 'Centrality at target');
58
+
59
+ return;
60
+ });
61
+ } catch (err) {
62
+ equal(err, null, 'Expected no error');
63
+ } finally {
64
+ await kill({});
65
+ }
58
66
 
59
67
  return end();
60
68
  });
@@ -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');
@@ -9,9 +9,9 @@ const {spawnLnd} = require('./../macros');
9
9
  const {waitForTermination} = require('./../macros');
10
10
 
11
11
  const all = promise => Promise.all(promise);
12
- const interval = 100;
12
+ const interval = 200;
13
13
  const nodes = [{watchers: true}, {tower: true}];
14
- const times = 200;
14
+ const times = 1000;
15
15
 
16
16
  // Disconnecting a watchtower should remove a watchtower
17
17
  test(`Disconnect watchtower`, async ({end, equal, match}) => {
@@ -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
+ });