ln-service 57.14.1 → 57.14.3

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,6 +1,6 @@
1
1
  # Versions
2
2
 
3
- ## 57.14.1
3
+ ## 57.14.3
4
4
 
5
5
  - `getChannel`: Add support for specifying `transaction_id` and
6
6
  `transaction_vout` instead of `id`
package/package.json CHANGED
@@ -7,9 +7,9 @@
7
7
  "url": "https://github.com/alexbosworth/ln-service/issues"
8
8
  },
9
9
  "dependencies": {
10
- "bolt07": "1.9.3",
10
+ "bolt07": "1.9.4",
11
11
  "invoices": "3.0.0",
12
- "lightning": "10.14.0",
12
+ "lightning": "10.14.1",
13
13
  "macaroon": "3.0.4"
14
14
  },
15
15
  "description": "Interaction helper for your Lightning Network daemon",
@@ -24,7 +24,7 @@
24
24
  "bn.js": "5.2.1",
25
25
  "bs58check": "3.0.1",
26
26
  "ecpair": "2.1.0",
27
- "ln-docker-daemons": "6.0.18",
27
+ "ln-docker-daemons": "6.0.19",
28
28
  "p2tr": "2.0.0",
29
29
  "portfinder": "1.0.32",
30
30
  "psbt": "3.0.0",
@@ -72,5 +72,5 @@
72
72
  "integration-test-0.14.4": "DOCKER_LND_VERSION=v0.14.4-beta npm run test",
73
73
  "test": "echo $DOCKER_LND_VERSION && node test/runner"
74
74
  },
75
- "version": "57.14.1"
75
+ "version": "57.14.3"
76
76
  }
@@ -24,128 +24,130 @@ const tokens = 1000;
24
24
 
25
25
  // Getting a route to a destination should return a route to the destination
26
26
  test(`Get a route to a destination`, async () => {
27
- const {kill, nodes} = await spawnLightningCluster({size});
27
+ await asyncRetry({}, async () => {
28
+ const {kill, nodes} = await spawnLightningCluster({size});
28
29
 
29
- const [control, target, remote] = nodes;
30
+ const [control, target, remote] = nodes;
30
31
 
31
- await control.generate({count: 500});
32
+ await control.generate({count: 200});
32
33
 
33
- await setupChannel({
34
- generate: control.generate,
35
- lnd: control.lnd,
36
- to: target,
37
- });
38
-
39
- await setupChannel({
40
- generate: target.generate,
41
- give_tokens: 1e5,
42
- is_private: true,
43
- lnd: target.lnd,
44
- to: remote,
45
- });
46
-
47
- const invoice = await asyncRetry({interval, times}, async () => {
48
- const invoice = await createInvoice({
49
- tokens,
50
- is_including_private_channels: true,
51
- lnd: remote.lnd,
34
+ await setupChannel({
35
+ generate: control.generate,
36
+ lnd: control.lnd,
37
+ to: target,
52
38
  });
53
39
 
54
- const {routes} = parsePaymentRequest({request: invoice.request});
55
-
56
- // Wait for private routes to get picked up
57
- if (!routes) {
58
- await cancelHodlInvoice({id: invoice.id, lnd: remote.lnd});
40
+ await setupChannel({
41
+ generate: target.generate,
42
+ give_tokens: 1e5,
43
+ is_private: true,
44
+ lnd: target.lnd,
45
+ to: remote,
46
+ });
59
47
 
60
- throw new Error('ExpectedRouteForInvoice');
61
- }
48
+ const invoice = await asyncRetry({interval, times}, async () => {
49
+ const invoice = await createInvoice({
50
+ tokens,
51
+ is_including_private_channels: true,
52
+ lnd: remote.lnd,
53
+ });
62
54
 
63
- return invoice;
64
- });
55
+ const {routes} = parsePaymentRequest({request: invoice.request});
65
56
 
66
- const parsed = parsePaymentRequest({request: invoice.request});
57
+ // Wait for private routes to get picked up
58
+ if (!routes) {
59
+ await cancelHodlInvoice({id: invoice.id, lnd: remote.lnd});
67
60
 
68
- await asyncRetry({interval, times}, async () => {
69
- const {route} = await getRouteToDestination({
70
- destination: parsed.destination,
71
- features: parsed.features,
72
- lnd: control.lnd,
73
- payment: parsed.payment,
74
- routes: parsed.routes,
75
- mtokens: parsed.mtokens,
76
- total_mtokens: parsed.mtokens,
77
- });
61
+ throw new Error('ExpectedRouteForInvoice');
62
+ }
78
63
 
79
- const paid = await payViaRoutes({
80
- id: parsed.id,
81
- lnd: control.lnd,
82
- routes: [route],
64
+ return invoice;
83
65
  });
84
66
 
85
- strictEqual(invoice.secret, paid.secret, 'Paid multi-hop private route');
86
- });
87
-
88
- const inv = await createInvoice({tokens, lnd: target.lnd});
67
+ const parsed = parsePaymentRequest({request: invoice.request});
89
68
 
90
- const invDetails = await decodePaymentRequest({
91
- lnd: control.lnd,
92
- request: inv.request,
93
- });
69
+ await asyncRetry({interval, times}, async () => {
70
+ const {route} = await getRouteToDestination({
71
+ destination: parsed.destination,
72
+ features: parsed.features,
73
+ lnd: control.lnd,
74
+ payment: parsed.payment,
75
+ routes: parsed.routes,
76
+ mtokens: parsed.mtokens,
77
+ total_mtokens: parsed.mtokens,
78
+ });
79
+
80
+ const paid = await payViaRoutes({
81
+ id: parsed.id,
82
+ lnd: control.lnd,
83
+ routes: [route],
84
+ });
94
85
 
95
- const controlToTarget = await getRouteToDestination({
96
- destination: target.id,
97
- features: invDetails.features,
98
- lnd: control.lnd,
99
- messages: [message],
100
- payment: invDetails.payment,
101
- tokens: invDetails.tokens / [control, remote].length,
102
- total_mtokens: invDetails.mtokens,
103
- });
86
+ strictEqual(invoice.secret, paid.secret, 'Paid multi-hop private route');
87
+ });
104
88
 
105
- const remoteToTarget = await getRouteToDestination({
106
- destination: target.id,
107
- features: invDetails.features,
108
- lnd: remote.lnd,
109
- messages: [message],
110
- payment: invDetails.payment,
111
- tokens: invDetails.tokens / [control, remote].length,
112
- total_mtokens: invDetails.mtokens,
113
- });
89
+ const inv = await createInvoice({tokens, lnd: target.lnd});
114
90
 
115
- try {
116
- const [controlPay, remotePay] = await all([
117
- payViaRoutes({
118
- id: invDetails.id,
119
- lnd: control.lnd,
120
- routes: [controlToTarget.route],
121
- }),
122
- payViaRoutes({
123
- id: invDetails.id,
124
- lnd: remote.lnd,
125
- routes: [remoteToTarget.route],
126
- }),
127
- ]);
91
+ const invDetails = await decodePaymentRequest({
92
+ lnd: control.lnd,
93
+ request: inv.request,
94
+ });
128
95
 
129
- strictEqual(controlPay.secret, inv.secret, 'Control paid for secret');
130
- strictEqual(remotePay.secret, inv.secret, 'Remote paid for secret');
96
+ const controlToTarget = await getRouteToDestination({
97
+ destination: target.id,
98
+ features: invDetails.features,
99
+ lnd: control.lnd,
100
+ messages: [message],
101
+ payment: invDetails.payment,
102
+ tokens: invDetails.tokens / [control, remote].length,
103
+ total_mtokens: invDetails.mtokens,
104
+ });
131
105
 
132
- const {payments} = await getInvoice({
133
- id: invDetails.id,
134
- lnd: target.lnd,
106
+ const remoteToTarget = await getRouteToDestination({
107
+ destination: target.id,
108
+ features: invDetails.features,
109
+ lnd: remote.lnd,
110
+ messages: [message],
111
+ payment: invDetails.payment,
112
+ tokens: invDetails.tokens / [control, remote].length,
113
+ total_mtokens: invDetails.mtokens,
135
114
  });
136
115
 
137
- const [payment1, payment2] = payments;
116
+ try {
117
+ const [controlPay, remotePay] = await all([
118
+ payViaRoutes({
119
+ id: invDetails.id,
120
+ lnd: control.lnd,
121
+ routes: [controlToTarget.route],
122
+ }),
123
+ payViaRoutes({
124
+ id: invDetails.id,
125
+ lnd: remote.lnd,
126
+ routes: [remoteToTarget.route],
127
+ }),
128
+ ]);
129
+
130
+ strictEqual(controlPay.secret, inv.secret, 'Control paid for secret');
131
+ strictEqual(remotePay.secret, inv.secret, 'Remote paid for secret');
132
+
133
+ const {payments} = await getInvoice({
134
+ id: invDetails.id,
135
+ lnd: target.lnd,
136
+ });
137
+
138
+ const [payment1, payment2] = payments;
138
139
 
139
- const [message1] = payment1.messages;
140
- const [message2] = payment2.messages;
140
+ const [message1] = payment1.messages;
141
+ const [message2] = payment2.messages;
141
142
 
142
- deepStrictEqual(message1, message, 'Target received message');
143
- deepStrictEqual(message2, message, 'Target received both messages');
144
- } catch (err) {
145
- strictEqual(err, null, 'Unexpected error paying invoice');
146
- }
143
+ deepStrictEqual(message1, message, 'Target received message');
144
+ deepStrictEqual(message2, message, 'Target received both messages');
145
+ } catch (err) {
146
+ strictEqual(err, null, 'Unexpected error paying invoice');
147
+ }
147
148
 
148
- await kill({});
149
+ await kill({});
150
+ });
149
151
 
150
152
  return;
151
153
  });
@@ -0,0 +1,323 @@
1
+ const {equal} = require('node:assert').strict;
2
+ const test = require('node:test');
3
+
4
+ const asyncRetry = require('async/retry');
5
+ const {setupChannel} = require('ln-docker-daemons');
6
+ const {spawnLightningCluster} = require('ln-docker-daemons');
7
+
8
+ const {addPeer} = require('./../../');
9
+ const {createInvoice} = require('./../../');
10
+ const {decodePaymentRequest} = require('./../../');
11
+ const {getHeight} = require('./../../');
12
+ const {getChannel} = require('./../../');
13
+ const {getNode} = require('./../../');
14
+ const {getRouteThroughHops} = require('./../../');
15
+ const {getRouteToDestination} = require('./../../');
16
+ const {getWalletInfo} = require('./../../');
17
+ const {payViaRoutes} = require('./../../');
18
+ const {routeFromChannels} = require('./../../');
19
+ const {updateRoutingFees} = require('./../../');
20
+ const {waitForRoute} = require('./../macros');
21
+
22
+ const baseFee = '1000';
23
+ const confirmationCount = 6;
24
+ const discount = '1000';
25
+ const interval = 10;
26
+ const size = 5;
27
+ const times = 1000;
28
+ const tokens = 100;
29
+
30
+ // Calculating a route from complex channels set should result in a route
31
+ test(`Get route through complex hops`, async () => {
32
+ const {kill, nodes} = await spawnLightningCluster({size});
33
+
34
+ try {
35
+ const [{generate, lnd}, target, remote, far, farthest] = nodes;
36
+
37
+ const controlToTargetChan = await setupChannel({
38
+ generate,
39
+ lnd,
40
+ to: target,
41
+ });
42
+
43
+ await generate({});
44
+
45
+ const targetRemoteChannel = await asyncRetry({
46
+ interval,
47
+ times,
48
+ },
49
+ async () => {
50
+ await generate({});
51
+
52
+ await addPeer({
53
+ lnd: target.lnd,
54
+ public_key: remote.id,
55
+ socket: remote.socket,
56
+ });
57
+
58
+ return await setupChannel({
59
+ generate: target.generate,
60
+ lnd: target.lnd,
61
+ to: remote,
62
+ });
63
+ });
64
+
65
+ await generate({});
66
+
67
+ const remoteFarChannel = await asyncRetry({interval, times}, async () => {
68
+ await generate({});
69
+
70
+ await addPeer({lnd: remote.lnd, public_key: far.id, socket: far.socket});
71
+
72
+ return await setupChannel({
73
+ generate: remote.generate,
74
+ lnd: remote.lnd,
75
+ to: far,
76
+ });
77
+ });
78
+
79
+ await target.generate({count: confirmationCount});
80
+
81
+ const farFarthestChannel = await asyncRetry({
82
+ interval,
83
+ times,
84
+ },
85
+ async () => {
86
+ await generate({});
87
+
88
+ await addPeer({
89
+ lnd: far.lnd,
90
+ public_key: farthest.id,
91
+ socket: farthest.socket,
92
+ });
93
+
94
+ return await setupChannel({
95
+ generate: far.generate,
96
+ lnd: far.lnd,
97
+ to: farthest,
98
+ });
99
+ });
100
+
101
+ await target.generate({count: confirmationCount});
102
+
103
+ await asyncRetry({interval, times}, async () => {
104
+ const wallet = await getWalletInfo({lnd: remote.lnd});
105
+
106
+ await addPeer({lnd, public_key: remote.id, socket: remote.socket});
107
+
108
+ if (!wallet.is_synced_to_chain) {
109
+ throw new Error('ExpectedWalletSyncedToChain');
110
+ }
111
+ });
112
+
113
+ await asyncRetry({interval, times}, async () => {
114
+ const wallet = await getWalletInfo({lnd: far.lnd});
115
+
116
+ await addPeer({lnd, public_key: far.id, socket: far.socket});
117
+
118
+ if (!wallet.is_synced_to_chain) {
119
+ throw new Error('ExpectedWalletSyncedToChain');
120
+ }
121
+ });
122
+
123
+ await asyncRetry({interval, times}, async () => {
124
+ const wallet = await getWalletInfo({lnd: farthest.lnd});
125
+
126
+ await addPeer({lnd, public_key: farthest.id, socket: farthest.socket});
127
+
128
+ if (!wallet.is_synced_to_chain) {
129
+ throw new Error('ExpectedWalletSyncedToChain');
130
+ }
131
+ });
132
+
133
+ await asyncRetry({interval, times}, async () => {
134
+ await getNode({lnd, public_key: remote.id});
135
+ });
136
+
137
+ await asyncRetry({interval, times}, async () => {
138
+ const {features} = await getNode({lnd, public_key: far.id});
139
+
140
+ if (!features.length) {
141
+ throw new Error('ExpectedFarFeatures');
142
+ }
143
+ });
144
+
145
+ await asyncRetry({interval, times}, async () => {
146
+ const {features} = await getNode({lnd, public_key: farthest.id});
147
+
148
+ if (!features.length) {
149
+ throw new Error('ExpectedFarthestFeatures');
150
+ }
151
+ });
152
+
153
+ const invoice = await createInvoice({
154
+ tokens,
155
+ cltv_delta: 50,
156
+ lnd: farthest.lnd,
157
+ });
158
+
159
+ const {id} = invoice;
160
+ const {request} = invoice;
161
+
162
+ const decodedRequest = await decodePaymentRequest({lnd, request});
163
+
164
+ await waitForRoute({
165
+ lnd,
166
+ destination: farthest.id,
167
+ tokens: invoice.tokens,
168
+ });
169
+
170
+ const {route} = await asyncRetry({interval, times}, async () => {
171
+ return await getRouteToDestination({
172
+ lnd,
173
+ cltv_delta: decodedRequest.cltv_delta,
174
+ destination: decodedRequest.destination,
175
+ tokens: invoice.tokens,
176
+ });
177
+ });
178
+
179
+ const policyAStart = await getChannel({lnd, id: controlToTargetChan.id});
180
+ const policyBStart = await getChannel({lnd, id: targetRemoteChannel.id});
181
+ const policyCStart = await getChannel({lnd, id: remoteFarChannel.id});
182
+ const policyDStart = await getChannel({lnd, id: farFarthestChannel.id});
183
+
184
+ await updateRoutingFees({
185
+ cltv_delta: 200,
186
+ lnd: target.lnd,
187
+ transaction_id: targetRemoteChannel.transaction_id,
188
+ transaction_vout: targetRemoteChannel.transaction_vout,
189
+ });
190
+
191
+ await updateRoutingFees({
192
+ cltv_delta: 200,
193
+ lnd: remote.lnd,
194
+ transaction_id: targetRemoteChannel.transaction_id,
195
+ transaction_vout: targetRemoteChannel.transaction_vout,
196
+ });
197
+
198
+ await updateRoutingFees({
199
+ cltv_delta: 100,
200
+ inbound_base_discount_mtokens: discount,
201
+ lnd: target.lnd,
202
+ transaction_id: controlToTargetChan.transaction_id,
203
+ transaction_vout: controlToTargetChan.transaction_vout,
204
+ });
205
+
206
+ await updateRoutingFees({
207
+ cltv_delta: 300,
208
+ lnd: far.lnd,
209
+ transaction_id: remoteFarChannel.transaction_id,
210
+ transaction_vout: remoteFarChannel.transaction_vout,
211
+ });
212
+
213
+ await updateRoutingFees({
214
+ cltv_delta: 400,
215
+ lnd: remote.lnd,
216
+ transaction_id: remoteFarChannel.transaction_id,
217
+ transaction_vout: remoteFarChannel.transaction_vout,
218
+ });
219
+
220
+ // Control updates their routing policy
221
+ await updateRoutingFees({
222
+ lnd,
223
+ cltv_delta: 100,
224
+ transaction_id: controlToTargetChan.transaction_id,
225
+ transaction_vout: controlToTargetChan.transaction_vout,
226
+ });
227
+
228
+ await updateRoutingFees({
229
+ cltv_delta: 500,
230
+ lnd: far.lnd,
231
+ transaction_id: farFarthestChannel.transaction_id,
232
+ transaction_vout: farFarthestChannel.transaction_vout,
233
+ });
234
+
235
+ await updateRoutingFees({
236
+ cltv_delta: 100,
237
+ lnd: farthest.lnd,
238
+ transaction_id: farFarthestChannel.transaction_id,
239
+ transaction_vout: farFarthestChannel.transaction_vout,
240
+ });
241
+
242
+ // Wait for policy to be updated
243
+ const policyA = await asyncRetry({interval, times}, async () => {
244
+ const policy = await getChannel({lnd, id: controlToTargetChan.id});
245
+
246
+ if (policy.updated_at === policyAStart.updated_at) {
247
+ throw new Error('PolicyNotUpdatedYet');
248
+ }
249
+
250
+ return policy;
251
+ });
252
+
253
+ // A discount should be set for traffic from control to remote
254
+ const discountFee = policyA.policies.find(n => n.public_key === target.id);
255
+
256
+ // Wait for policy to be updated
257
+ const policyB = await asyncRetry({interval, times}, async () => {
258
+ const policy = await getChannel({lnd, id: targetRemoteChannel.id});
259
+
260
+ if (policy.updated_at === policyBStart.updated_at) {
261
+ throw new Error('PolicyNotUpdatedYet');
262
+ }
263
+
264
+ return policy;
265
+ });
266
+
267
+ // Wait for policy to be updated
268
+ const policyC = await asyncRetry({interval, times}, async () => {
269
+ const policy = await getChannel({lnd, id: remoteFarChannel.id});
270
+
271
+ if (policy.updated_at === policyCStart.updated_at) {
272
+ throw new Error('PolicyNotUpdatedYet');
273
+ }
274
+
275
+ return policy;
276
+ });
277
+
278
+ const policyD = await asyncRetry({interval, times}, async () => {
279
+ const policy = await getChannel({lnd, id: farFarthestChannel.id});
280
+
281
+ if (policy.updated_at === policyDStart.updated_at) {
282
+ throw new Error('PolicyNotUpdatedYet');
283
+ }
284
+
285
+ return policy;
286
+ });
287
+
288
+ const channelsRoute = routeFromChannels({
289
+ channels: [policyA, policyB, policyC, policyD],
290
+ cltv_delta: decodedRequest.cltv_delta + confirmationCount,
291
+ destination: decodedRequest.destination,
292
+ height: (await getHeight({lnd})).current_block_height,
293
+ mtokens: decodedRequest.mtokens,
294
+ payment: decodedRequest.payment,
295
+ total_mtokens: decodedRequest.mtokens,
296
+ });
297
+
298
+ const lndRoute = await getRouteThroughHops({
299
+ lnd,
300
+ cltv_delta: decodedRequest.cltv_delta + confirmationCount,
301
+ mtokens: decodedRequest.mtokens,
302
+ payment: decodedRequest.payment,
303
+ public_keys: [target.id, remote.id, far.id, farthest.id],
304
+ total_mtokens: decodedRequest.mtokens,
305
+ });
306
+
307
+ const discounted = BigInt(discountFee.inbound_base_discount_mtokens);
308
+
309
+ const gotTotalFee = BigInt(channelsRoute.route.fee_mtokens);
310
+
311
+ await payViaRoutes({lnd, id: invoice.id, routes: [channelsRoute.route]});
312
+
313
+ equal(gotTotalFee, BigInt(baseFee * 2), 'Got expected discount');
314
+
315
+ await kill({});
316
+ } catch (err) {
317
+ await kill({});
318
+
319
+ equal(err, null, 'Expected no error');
320
+ }
321
+
322
+ return;
323
+ });
@@ -64,7 +64,11 @@ test(`Get route through hops`, async () => {
64
64
  }
65
65
  });
66
66
 
67
- const invoice = await createInvoice({tokens, lnd: remote.lnd});
67
+ const invoice = await createInvoice({
68
+ tokens,
69
+ cltv_delta: 60,
70
+ lnd: remote.lnd,
71
+ });
68
72
 
69
73
  const {id} = invoice;
70
74
  const {request} = invoice;
@@ -122,6 +126,15 @@ test(`Get route through hops`, async () => {
122
126
  total_mtokens: decodedRequest.mtokens,
123
127
  });
124
128
 
129
+ const lndRoute = await getRouteThroughHops({
130
+ lnd,
131
+ cltv_delta: decodedRequest.cltv_delta + confirmationCount,
132
+ mtokens: decodedRequest.mtokens,
133
+ payment: decodedRequest.payment,
134
+ public_keys: [target.id, remote.id],
135
+ total_mtokens: decodedRequest.mtokens,
136
+ });
137
+
125
138
  const discounted = BigInt(discountFee.inbound_base_discount_mtokens);
126
139
 
127
140
  const gotTotalFee = BigInt(channelsRoute.route.fee_mtokens);
@@ -133,4 +146,4 @@ test(`Get route through hops`, async () => {
133
146
  equal(gotTotalFee, BigInt(baseFee) - discounted, 'Got expected discount');
134
147
 
135
148
  return;
136
- });
149
+ });