paid-services 10.2.0 → 10.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 +5 -0
- package/network/constants.json +3 -1
- package/network/get_path_after_self.js +222 -0
- package/network/get_path_outbound.js +91 -7
- package/network/request_network_reply.js +8 -5
- package/network/return_network_response.js +33 -17
- package/network/service_network_messages.js +23 -14
- package/offers/get_offer_invoice.js +5 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## Version 10.3.0
|
|
4
|
+
|
|
5
|
+
- `getOfferInvoice`: Add support for offer paths that start at the payer
|
|
6
|
+
- `serviceSupportOffer`: Add support for reply paths that start at the node
|
|
7
|
+
|
|
3
8
|
## Version 10.2.0
|
|
4
9
|
|
|
5
10
|
- `serviceSupportOffer`: Add `start` event with the support offer
|
package/network/constants.json
CHANGED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {decryptBlindedPath} = require('bolt04');
|
|
3
|
+
const {diffieHellmanComputeSecret} = require('ln-service');
|
|
4
|
+
const {getChannel} = require('ln-service');
|
|
5
|
+
const {getChannels} = require('ln-service');
|
|
6
|
+
const {returnResult} = require('asyncjs-util');
|
|
7
|
+
|
|
8
|
+
const {knownHopRecordTypes} = require('./constants');
|
|
9
|
+
const {typePathId} = require('./constants');
|
|
10
|
+
|
|
11
|
+
const hasPathId = records => records.some(n => n.type === typePathId);
|
|
12
|
+
const hopsAfterFirst = hops => hops.slice(1);
|
|
13
|
+
const idsOf = channel => [channel.id].concat(channel.other_ids);
|
|
14
|
+
const {isArray} = Array;
|
|
15
|
+
const isEven = type => !(BigInt(type) % BigInt(2));
|
|
16
|
+
const isKnown = record => knownHopRecordTypes.includes(record.type);
|
|
17
|
+
const isPublicKey = n => typeof n === 'string' && /^[0-9A-F]{66}$/i.test(n);
|
|
18
|
+
const isUnknownEven = record => !isKnown(record) && isEven(record.type);
|
|
19
|
+
|
|
20
|
+
/** Get the blinded path after self, for a blinded path that starts at self
|
|
21
|
+
|
|
22
|
+
Self reads its own hop as an introduction node would. The path goes on from
|
|
23
|
+
the next node, which the hop names, or which is the peer at the other end
|
|
24
|
+
of the channel that the hop names.
|
|
25
|
+
|
|
26
|
+
As BOLT 4 asks of a node that forwards, the hop of self is not used when it
|
|
27
|
+
has an unknown even record, when it has a path id, or when it allows
|
|
28
|
+
features, since no onion message features are defined.
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
hops: [{
|
|
32
|
+
encrypted_data: <Encrypted Recipient Data Hex String>
|
|
33
|
+
relay_key: <Blinded Relaying Node Public Key Hex String>
|
|
34
|
+
}]
|
|
35
|
+
identity: <Node Identity Public Key Hex String>
|
|
36
|
+
key: <First Hop Path Key Public Key Hex String>
|
|
37
|
+
lnd: <Authenticated LND API Object>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@returns via cbk or Promise
|
|
41
|
+
{
|
|
42
|
+
hops: [{
|
|
43
|
+
encrypted_data: <Encrypted Recipient Data Hex String>
|
|
44
|
+
relay_key: <Blinded Relaying Node Public Key Hex String>
|
|
45
|
+
}]
|
|
46
|
+
introduction_node: <Next Node Public Key Hex String>
|
|
47
|
+
key: <Next Hop Path Key Public Key Hex String>
|
|
48
|
+
}
|
|
49
|
+
*/
|
|
50
|
+
module.exports = (args, cbk) => {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
return asyncAuto({
|
|
53
|
+
// Check arguments
|
|
54
|
+
validate: cbk => {
|
|
55
|
+
if (!isArray(args.hops) || !args.hops.length) {
|
|
56
|
+
return cbk([400, 'ExpectedBlindedPathHopsToGetPathAfterSelf']);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!isPublicKey(args.identity)) {
|
|
60
|
+
return cbk([400, 'ExpectedIdentityPublicKeyToGetPathAfterSelf']);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!args.key) {
|
|
64
|
+
return cbk([400, 'ExpectedPathKeyToGetPathAfterSelf']);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!args.lnd) {
|
|
68
|
+
return cbk([400, 'ExpectedLndToGetPathAfterSelf']);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return cbk();
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// Compute the shared secret of the path key and the node identity key
|
|
75
|
+
getSecret: ['validate', ({}, cbk) => {
|
|
76
|
+
return diffieHellmanComputeSecret({
|
|
77
|
+
lnd: args.lnd,
|
|
78
|
+
partner_public_key: args.key,
|
|
79
|
+
},
|
|
80
|
+
cbk);
|
|
81
|
+
}],
|
|
82
|
+
|
|
83
|
+
// Decrypt the data of the hop of self to read where the path goes next
|
|
84
|
+
decrypt: ['getSecret', ({getSecret}, cbk) => {
|
|
85
|
+
const [own] = args.hops;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
return cbk(null, decryptBlindedPath({
|
|
89
|
+
encrypted: own.encrypted_data,
|
|
90
|
+
key: args.key,
|
|
91
|
+
secret: getSecret.secret,
|
|
92
|
+
}));
|
|
93
|
+
} catch (err) {
|
|
94
|
+
return cbk([503, 'FailedToDecryptOwnBlindedPathHop', {err}]);
|
|
95
|
+
}
|
|
96
|
+
}],
|
|
97
|
+
|
|
98
|
+
// Check the hop of self can be followed, as a forwarding node would
|
|
99
|
+
check: ['decrypt', ({decrypt}, cbk) => {
|
|
100
|
+
// An unknown even record cannot be ignored, so the hop cannot be used
|
|
101
|
+
if (decrypt.records.some(isUnknownEven)) {
|
|
102
|
+
return cbk([
|
|
103
|
+
400,
|
|
104
|
+
'UnexpectedUnknownRequiredRecordInOwnBlindedPathHop',
|
|
105
|
+
]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// No features are defined for onion messages, so a feature bit in
|
|
109
|
+
// the allowed features is an unknown feature, and the hop is not used
|
|
110
|
+
if (!!decrypt.features.length) {
|
|
111
|
+
return cbk([400, 'UnsupportedFeatureInOwnBlindedPathHop']);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// A hop with no next node or channel is the final hop of the path
|
|
115
|
+
if (!decrypt.next_node_id && !decrypt.next_channel_id) {
|
|
116
|
+
return cbk([400, 'UnexpectedBlindedPathToSelf']);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// A hop that goes on to a next node is not one with a path id record,
|
|
120
|
+
// which is present even when the path id is empty
|
|
121
|
+
if (hasPathId(decrypt.records)) {
|
|
122
|
+
return cbk([400, 'UnexpectedPathIdInOwnBlindedPathHop']);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return cbk();
|
|
126
|
+
}],
|
|
127
|
+
|
|
128
|
+
// The hops after self, which the path goes on over
|
|
129
|
+
hops: ['check', ({}, cbk) => {
|
|
130
|
+
const hops = hopsAfterFirst(args.hops);
|
|
131
|
+
|
|
132
|
+
// The next node has a hop of its own to send to
|
|
133
|
+
if (!hops.length) {
|
|
134
|
+
return cbk([400, 'ExpectedHopsAfterSelfInBlindedPath']);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return cbk(null, hops);
|
|
138
|
+
}],
|
|
139
|
+
|
|
140
|
+
// Get the channels of self when the next node is named by a channel
|
|
141
|
+
getChannels: ['decrypt', 'hops', ({decrypt}, cbk) => {
|
|
142
|
+
// Exit early when the hop names the next node
|
|
143
|
+
if (!!decrypt.next_node_id) {
|
|
144
|
+
return cbk();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return getChannels({lnd: args.lnd}, cbk);
|
|
148
|
+
}],
|
|
149
|
+
|
|
150
|
+
// Find the next channel among the channels of self
|
|
151
|
+
channel: ['decrypt', 'getChannels', ({decrypt, getChannels}, cbk) => {
|
|
152
|
+
// Exit early when the hop names the next node
|
|
153
|
+
if (!getChannels) {
|
|
154
|
+
return cbk();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const id = decrypt.next_channel_id;
|
|
158
|
+
|
|
159
|
+
// A private channel can be named by one of its aliases
|
|
160
|
+
return cbk(null, getChannels.channels.find(n => idsOf(n).includes(id)));
|
|
161
|
+
}],
|
|
162
|
+
|
|
163
|
+
// Look for the next channel in the graph when self does not list it
|
|
164
|
+
getChannel: ['channel', 'decrypt', ({channel, decrypt}, cbk) => {
|
|
165
|
+
// Exit early when the next node is already known
|
|
166
|
+
if (!!decrypt.next_node_id || !!channel) {
|
|
167
|
+
return cbk();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return getChannel({
|
|
171
|
+
id: decrypt.next_channel_id,
|
|
172
|
+
lnd: args.lnd,
|
|
173
|
+
},
|
|
174
|
+
(err, res) => {
|
|
175
|
+
// Exit early when the channel is not in the graph
|
|
176
|
+
if (!!err) {
|
|
177
|
+
return cbk([503, 'FailedToFindNextPeerInBlindedPath']);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return cbk(null, res);
|
|
181
|
+
});
|
|
182
|
+
}],
|
|
183
|
+
|
|
184
|
+
// The next node is the one that the hop names, or is across the channel
|
|
185
|
+
next: [
|
|
186
|
+
'channel',
|
|
187
|
+
'decrypt',
|
|
188
|
+
'getChannel',
|
|
189
|
+
({channel, decrypt, getChannel}, cbk) =>
|
|
190
|
+
{
|
|
191
|
+
// Exit early when the hop names the next node
|
|
192
|
+
if (!!decrypt.next_node_id) {
|
|
193
|
+
return cbk(null, decrypt.next_node_id);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Exit early when the channel is a channel of self
|
|
197
|
+
if (!!channel) {
|
|
198
|
+
return cbk(null, channel.partner_public_key);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const keys = getChannel.policies.map(n => n.public_key);
|
|
202
|
+
|
|
203
|
+
// The channel in the graph has to be one that self forwards over
|
|
204
|
+
if (!keys.includes(args.identity)) {
|
|
205
|
+
return cbk([503, 'FailedToFindNextPeerInBlindedPath']);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return cbk(null, keys.find(n => n !== args.identity));
|
|
209
|
+
}],
|
|
210
|
+
|
|
211
|
+
// The path goes on from the next node, with the next hop path key
|
|
212
|
+
path: ['decrypt', 'hops', 'next', ({decrypt, hops, next}, cbk) => {
|
|
213
|
+
return cbk(null, {
|
|
214
|
+
hops,
|
|
215
|
+
introduction_node: next,
|
|
216
|
+
key: decrypt.next_path_key,
|
|
217
|
+
});
|
|
218
|
+
}],
|
|
219
|
+
},
|
|
220
|
+
returnResult({reject, resolve, of: 'path'}, cbk));
|
|
221
|
+
});
|
|
222
|
+
};
|
|
@@ -1,28 +1,48 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncWhilst = require('async/whilst');
|
|
2
3
|
const {getChannel} = require('ln-service');
|
|
4
|
+
const {getIdentity} = require('ln-service');
|
|
3
5
|
const {returnResult} = require('asyncjs-util');
|
|
4
6
|
|
|
5
7
|
const getMessageHops = require('./get_message_hops');
|
|
8
|
+
const getPathAfterSelf = require('./get_path_after_self');
|
|
6
9
|
const pathIntroduction = require('./path_introduction');
|
|
7
10
|
|
|
8
11
|
const edgeChannel = edge => edge.split('x').slice(0, -1).join('x');
|
|
12
|
+
const {isArray} = Array;
|
|
13
|
+
const isPublicKey = n => typeof n === 'string' && /^[0-9A-F]{66}$/i.test(n);
|
|
9
14
|
|
|
10
|
-
/** Get the outbound hops to reach the introduction node of a blinded path
|
|
15
|
+
/** Get the outbound hops to reach the introduction node of a blinded path,
|
|
16
|
+
with the hops and the path key to send over
|
|
11
17
|
|
|
12
18
|
When the introduction node is not a connected peer, the message is relayed
|
|
13
19
|
along a payment route through nodes that relay onion messages
|
|
14
20
|
|
|
21
|
+
A path that starts at self is sent from the next node of the path: self
|
|
22
|
+
reads its own hops, as an introduction node would, and sends over the hops
|
|
23
|
+
after them. A path that does not start at self is sent over as it is.
|
|
24
|
+
|
|
15
25
|
{
|
|
16
26
|
[is_expecting_reply]: <Reply Comes Back Along the Same Nodes Bool>
|
|
17
27
|
lnd: <Authenticated LND API Object>
|
|
18
28
|
path: {
|
|
29
|
+
hops: [{
|
|
30
|
+
encrypted_data: <Encrypted Recipient Data Hex String>
|
|
31
|
+
relay_key: <Blinded Relaying Node Public Key Hex String>
|
|
32
|
+
}]
|
|
19
33
|
[introduction_edge]: <Introduction Node Edge Format Channel Id String>
|
|
20
34
|
[introduction_node]: <Introduction Node Public Key Hex String>
|
|
35
|
+
key: <First Hop Path Key Public Key Hex String>
|
|
21
36
|
}
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
@returns via cbk or Promise
|
|
25
40
|
{
|
|
41
|
+
hops: [{
|
|
42
|
+
encrypted_data: <Encrypted Recipient Data Hex String>
|
|
43
|
+
relay_key: <Blinded Relaying Node Public Key Hex String>
|
|
44
|
+
}]
|
|
45
|
+
key: <First Hop Path Key Public Key Hex String>
|
|
26
46
|
outbound: [<Relaying Node Public Key Hex String>]
|
|
27
47
|
}
|
|
28
48
|
*/
|
|
@@ -39,6 +59,21 @@ module.exports = (args, cbk) => {
|
|
|
39
59
|
return cbk([400, 'ExpectedBlindedPathToGetPathOutbound']);
|
|
40
60
|
}
|
|
41
61
|
|
|
62
|
+
if (!isArray(args.path.hops) || !args.path.hops.length) {
|
|
63
|
+
return cbk([400, 'ExpectedBlindedPathHopsToGetPathOutbound']);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// A node introduction is compared to self as a public key
|
|
67
|
+
if (!!args.path.introduction_node) {
|
|
68
|
+
if (!isPublicKey(args.path.introduction_node)) {
|
|
69
|
+
return cbk([400, 'ExpectedIntroductionNodeKeyToGetPathOutbound']);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!args.path.key) {
|
|
74
|
+
return cbk([400, 'ExpectedBlindedPathKeyToGetPathOutbound']);
|
|
75
|
+
}
|
|
76
|
+
|
|
42
77
|
return cbk();
|
|
43
78
|
},
|
|
44
79
|
|
|
@@ -54,6 +89,11 @@ module.exports = (args, cbk) => {
|
|
|
54
89
|
return getChannel({id, lnd: args.lnd}, cbk);
|
|
55
90
|
}],
|
|
56
91
|
|
|
92
|
+
// Get the identity key of self, which the path can start at
|
|
93
|
+
getIdentity: ['validate', ({}, cbk) => {
|
|
94
|
+
return getIdentity({lnd: args.lnd}, cbk);
|
|
95
|
+
}],
|
|
96
|
+
|
|
57
97
|
// Determine the introduction node of the path
|
|
58
98
|
introduction: ['getChannel', ({getChannel}, cbk) => {
|
|
59
99
|
try {
|
|
@@ -62,25 +102,69 @@ module.exports = (args, cbk) => {
|
|
|
62
102
|
path: args.path,
|
|
63
103
|
});
|
|
64
104
|
|
|
65
|
-
|
|
105
|
+
// Node keys are compared in lowercase, as LND gives them
|
|
106
|
+
return cbk(null, introduction.toLowerCase());
|
|
66
107
|
} catch (err) {
|
|
67
108
|
return cbk([400, err.message]);
|
|
68
109
|
}
|
|
69
110
|
}],
|
|
70
111
|
|
|
112
|
+
// Step past the hops of self while the path starts at self
|
|
113
|
+
stepped: [
|
|
114
|
+
'getIdentity',
|
|
115
|
+
'introduction',
|
|
116
|
+
({getIdentity, introduction}, cbk) =>
|
|
117
|
+
{
|
|
118
|
+
const identity = getIdentity.public_key;
|
|
119
|
+
|
|
120
|
+
let path = {
|
|
121
|
+
hops: args.path.hops,
|
|
122
|
+
introduction_node: introduction,
|
|
123
|
+
key: args.path.key,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Self reads its own hop as an introduction node would, and the path
|
|
127
|
+
// goes on from the next node, which can be self again
|
|
128
|
+
return asyncWhilst(
|
|
129
|
+
cbk => cbk(null, path.introduction_node === identity),
|
|
130
|
+
cbk => {
|
|
131
|
+
return getPathAfterSelf({
|
|
132
|
+
identity,
|
|
133
|
+
hops: path.hops,
|
|
134
|
+
key: path.key,
|
|
135
|
+
lnd: args.lnd,
|
|
136
|
+
},
|
|
137
|
+
(err, res) => {
|
|
138
|
+
if (!!err) {
|
|
139
|
+
return cbk(err);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
path = res;
|
|
143
|
+
|
|
144
|
+
return cbk();
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
err => !!err ? cbk(err) : cbk(null, path)
|
|
148
|
+
);
|
|
149
|
+
}],
|
|
150
|
+
|
|
71
151
|
// Get the hops to relay the message to the introduction node
|
|
72
|
-
getHops: ['
|
|
152
|
+
getHops: ['stepped', ({stepped}, cbk) => {
|
|
73
153
|
return getMessageHops({
|
|
74
|
-
destination:
|
|
154
|
+
destination: stepped.introduction_node,
|
|
75
155
|
is_expecting_reply: args.is_expecting_reply,
|
|
76
156
|
lnd: args.lnd,
|
|
77
157
|
},
|
|
78
158
|
cbk);
|
|
79
159
|
}],
|
|
80
160
|
|
|
81
|
-
// The outbound hops end at the introduction node
|
|
82
|
-
outbound: ['getHops', ({getHops}, cbk) => {
|
|
83
|
-
return cbk(null, {
|
|
161
|
+
// The outbound hops end at the introduction node of the path to send
|
|
162
|
+
outbound: ['getHops', 'stepped', ({getHops, stepped}, cbk) => {
|
|
163
|
+
return cbk(null, {
|
|
164
|
+
hops: stepped.hops,
|
|
165
|
+
key: stepped.key,
|
|
166
|
+
outbound: getHops.hops,
|
|
167
|
+
});
|
|
84
168
|
}],
|
|
85
169
|
},
|
|
86
170
|
returnResult({reject, resolve, of: 'outbound'}, cbk));
|
|
@@ -27,6 +27,9 @@ const isTimeout = n => isSafeInteger(n) && n > 0;
|
|
|
27
27
|
than `max_reply_hops` keeps the hops nearest self, and the recipient routes
|
|
28
28
|
to its start.
|
|
29
29
|
|
|
30
|
+
A path that starts at self is sent from the next node of the path, and the
|
|
31
|
+
reply path goes back from there.
|
|
32
|
+
|
|
30
33
|
{
|
|
31
34
|
lnd: <Authenticated LND API Object>
|
|
32
35
|
[max_reply_hops]: <Maximum Reply Path Hops Count Number>
|
|
@@ -135,9 +138,9 @@ module.exports = (args, cbk) => {
|
|
|
135
138
|
{
|
|
136
139
|
const {outbound} = getOutbound;
|
|
137
140
|
|
|
138
|
-
// The outbound hops end at the introduction node of the
|
|
139
|
-
// is the recipient itself when
|
|
140
|
-
const isRecipient =
|
|
141
|
+
// The outbound hops end at the introduction node of the hops that are
|
|
142
|
+
// sent, which is the recipient itself when there is a single hop
|
|
143
|
+
const isRecipient = getOutbound.hops.length === 1;
|
|
141
144
|
|
|
142
145
|
const relays = !isRecipient ? outbound : outbound.slice(0, -1);
|
|
143
146
|
|
|
@@ -258,8 +261,8 @@ module.exports = (args, cbk) => {
|
|
|
258
261
|
|
|
259
262
|
// Send the message with a reply path back the way it goes
|
|
260
263
|
const sending = sendMessage({
|
|
261
|
-
inbound:
|
|
262
|
-
key:
|
|
264
|
+
inbound: getOutbound.hops,
|
|
265
|
+
key: getOutbound.key,
|
|
263
266
|
lnd: args.lnd,
|
|
264
267
|
message: args.message,
|
|
265
268
|
outbound: getOutbound.outbound,
|
|
@@ -14,8 +14,8 @@ const largeOnionBytes = 32834;
|
|
|
14
14
|
|
|
15
15
|
With `is_regular`, the response is only sent when it fits in an onion
|
|
16
16
|
message of the regular size. Otherwise it has to fit in a large one. A
|
|
17
|
-
response that does not fit is a 413 error. Give the `
|
|
18
|
-
|
|
17
|
+
response that does not fit is a 413 error. Give the `route` to the reply
|
|
18
|
+
path when it is known, as found by `getPathOutbound`.
|
|
19
19
|
|
|
20
20
|
{
|
|
21
21
|
[is_regular]: <Only Send In An Onion Message Of The Regular Size Bool>
|
|
@@ -33,7 +33,14 @@ const largeOnionBytes = 32834;
|
|
|
33
33
|
[introduction_node]: <Introduction Node Public Key Hex String>
|
|
34
34
|
key: <Path Key Hex String>
|
|
35
35
|
}
|
|
36
|
-
[
|
|
36
|
+
[route]: {
|
|
37
|
+
hops: [{
|
|
38
|
+
encrypted_data: <Encrypted Data Hex String>
|
|
39
|
+
relay_key: <Blinded Relay Key Hex String>
|
|
40
|
+
}]
|
|
41
|
+
key: <Path Key Hex String>
|
|
42
|
+
outbound: [<Relaying Node Public Key Hex String>]
|
|
43
|
+
}
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
@returns via cbk or Promise
|
|
@@ -58,23 +65,32 @@ module.exports = (args, cbk) => {
|
|
|
58
65
|
return cbk();
|
|
59
66
|
},
|
|
60
67
|
|
|
61
|
-
// Determine the
|
|
62
|
-
|
|
63
|
-
// Exit early when the
|
|
64
|
-
if (!!args.
|
|
65
|
-
return cbk(null,
|
|
68
|
+
// Determine the route to the reply path: the hops and key to send over
|
|
69
|
+
getRoute: ['validate', ({}, cbk) => {
|
|
70
|
+
// Exit early when the route is already known
|
|
71
|
+
if (!!args.route) {
|
|
72
|
+
return cbk(null, args.route);
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
return getPathOutbound({
|
|
75
|
+
return getPathOutbound({
|
|
76
|
+
lnd: args.lnd,
|
|
77
|
+
path: {
|
|
78
|
+
hops: args.reply.inbound,
|
|
79
|
+
introduction_edge: args.reply.introduction_edge,
|
|
80
|
+
introduction_node: args.reply.introduction_node,
|
|
81
|
+
key: args.reply.key,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
cbk);
|
|
69
85
|
}],
|
|
70
86
|
|
|
71
87
|
// Check that the response fits in an onion message that can be sent
|
|
72
|
-
checkSize: ['
|
|
88
|
+
checkSize: ['getRoute', ({getRoute}, cbk) => {
|
|
73
89
|
try {
|
|
74
90
|
const {onion} = onionForPath({
|
|
75
|
-
inbound:
|
|
76
|
-
key:
|
|
77
|
-
outbound:
|
|
91
|
+
inbound: getRoute.hops,
|
|
92
|
+
key: getRoute.key,
|
|
93
|
+
outbound: getRoute.outbound,
|
|
78
94
|
records: [args.message],
|
|
79
95
|
});
|
|
80
96
|
|
|
@@ -99,13 +115,13 @@ module.exports = (args, cbk) => {
|
|
|
99
115
|
}],
|
|
100
116
|
|
|
101
117
|
// Send the response message through the reply path
|
|
102
|
-
send: ['checkSize', '
|
|
118
|
+
send: ['checkSize', 'getRoute', ({getRoute}, cbk) => {
|
|
103
119
|
return sendMessage({
|
|
104
|
-
inbound:
|
|
105
|
-
key:
|
|
120
|
+
inbound: getRoute.hops,
|
|
121
|
+
key: getRoute.key,
|
|
106
122
|
lnd: args.lnd,
|
|
107
123
|
message: args.message,
|
|
108
|
-
outbound:
|
|
124
|
+
outbound: getRoute.outbound,
|
|
109
125
|
},
|
|
110
126
|
cbk);
|
|
111
127
|
}],
|
|
@@ -176,23 +176,32 @@ module.exports = ({delay, ids, lnd}) => {
|
|
|
176
176
|
|
|
177
177
|
const {encrypted, key, reply} = received;
|
|
178
178
|
|
|
179
|
-
let
|
|
179
|
+
let route;
|
|
180
180
|
|
|
181
|
-
// The route to the reply path
|
|
182
|
-
|
|
181
|
+
// The route to the reply path, the hops and key to send over and the
|
|
182
|
+
// outbound hops to reach them, is found once for a message
|
|
183
|
+
const getRoute = () => {
|
|
183
184
|
// Exit early when the route was already found or is being found
|
|
184
|
-
if (!!
|
|
185
|
-
return
|
|
185
|
+
if (!!route) {
|
|
186
|
+
return route;
|
|
186
187
|
}
|
|
187
188
|
|
|
188
|
-
const lookup = getPathOutbound({
|
|
189
|
+
const lookup = getPathOutbound({
|
|
190
|
+
lnd,
|
|
191
|
+
path: {
|
|
192
|
+
hops: reply.inbound,
|
|
193
|
+
introduction_edge: reply.introduction_edge,
|
|
194
|
+
introduction_node: reply.introduction_node,
|
|
195
|
+
key: reply.key,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
189
198
|
|
|
190
|
-
|
|
199
|
+
route = lookup;
|
|
191
200
|
|
|
192
201
|
// A failed lookup is not kept, so that a later reply can try again
|
|
193
202
|
lookup.catch(() => {
|
|
194
|
-
if (
|
|
195
|
-
|
|
203
|
+
if (route === lookup) {
|
|
204
|
+
route = undefined;
|
|
196
205
|
}
|
|
197
206
|
});
|
|
198
207
|
|
|
@@ -224,7 +233,7 @@ module.exports = ({delay, ids, lnd}) => {
|
|
|
224
233
|
|
|
225
234
|
// The caller is told outside of the promise chain, so that a throw in
|
|
226
235
|
// the caller is not taken as a failure to send
|
|
227
|
-
return
|
|
236
|
+
return getRoute()
|
|
228
237
|
.then(res => {
|
|
229
238
|
// Exit early when the service stopped while the route was found
|
|
230
239
|
if (isStopped) {
|
|
@@ -236,7 +245,7 @@ module.exports = ({delay, ids, lnd}) => {
|
|
|
236
245
|
lnd,
|
|
237
246
|
reply,
|
|
238
247
|
message: response,
|
|
239
|
-
|
|
248
|
+
route: res,
|
|
240
249
|
});
|
|
241
250
|
})
|
|
242
251
|
.then(() => setImmediate(() => done()), err => {
|
|
@@ -253,11 +262,11 @@ module.exports = ({delay, ids, lnd}) => {
|
|
|
253
262
|
|
|
254
263
|
// The caller is told outside of the promise chain, so that it is only
|
|
255
264
|
// called once, even when it throws
|
|
256
|
-
return
|
|
265
|
+
return getRoute()
|
|
257
266
|
.then(res => regularReplyBytes({
|
|
258
267
|
type,
|
|
259
|
-
inbound:
|
|
260
|
-
key:
|
|
268
|
+
inbound: res.hops,
|
|
269
|
+
key: res.key,
|
|
261
270
|
outbound: res.outbound,
|
|
262
271
|
}))
|
|
263
272
|
.then(res => setImmediate(() => cbk(null, res)), err => {
|
|
@@ -29,11 +29,11 @@ const typeSchnorr = 'schnorr';
|
|
|
29
29
|
The request is sent over each path of the offer in turn, waiting `timeout`
|
|
30
30
|
for each, until a path gives back an invoice, or the recipient replies
|
|
31
31
|
with an invoice error, which is returned. An offer without paths has the
|
|
32
|
-
request sent to its issuer id.
|
|
33
|
-
its
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
supported.
|
|
32
|
+
request sent to its issuer id. A path that starts at the payer is sent
|
|
33
|
+
from its next node. The reply path of a request goes back along its route,
|
|
34
|
+
so that the recipient can reach it. The route to the start of a path is
|
|
35
|
+
found over the public graph and the channels of the payer, so a payer that
|
|
36
|
+
is only connected through a peer that is itself private is not supported.
|
|
37
37
|
|
|
38
38
|
The request is signed by the `payer_id`, a new key that LND derives in key
|
|
39
39
|
family 808 at `payer_key_index`, only once the request is known to be one
|
package/package.json
CHANGED
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"integration-tests": "node test/integration",
|
|
74
74
|
"test": "node --experimental-test-coverage --test test/actions/*.js test/balanced/*.js test/client/*.js test/config/*.js test/network/*.js test/offers/*.js test/p2p/*.js test/records/*.js test/respond/*.js test/server/*.js test/service_records/*.js test/swaps/*.js test/services/*.js test/support/*.js test/trades/*.js"
|
|
75
75
|
},
|
|
76
|
-
"version": "10.
|
|
76
|
+
"version": "10.3.0"
|
|
77
77
|
}
|