forked from alexbosworth/ln-service
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcalculate_hops.js
183 lines (145 loc) · 4.51 KB
/
calculate_hops.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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
const PriorityQueue = require('@datastructures-js/priority-queue');
const policyFee = require('./policy_fee');
const decBase = 10;
const minCltvDelta = 0;
const minFee = 0;
const mtokPerTok = 1e3;
const queueStart = 1;
/** Calculate hops between start and end nodes
{
channels: [{
capacity: <Capacity Tokens Number>
id: <Standard Channel Id String>
policies: [{
base_fee_mtokens: <Base Fee Millitokens String>
cltv_delta: <CLTV Delta Number>
fee_rate: <Fee Rate Number>
is_disabled: <Channel is Disabled Bool>
min_htlc_mtokens: <Minimum HTLC Millitokens String>
public_key: <Public Key Hex String>
}]
}]
end: <End Public Key Hex String>
[ignore]: [{
[channel]: <Standard Format Channel Id String>
public_key: <Public Key Hex String>
}]
mtokens: <Millitokens Number>
start: <Start Public Key Hex String>
}
@throws
<Error>
@returns
{
[hops]: [{
base_fee_mtokens: <Base Fee Millitokens String>
channel: <Standard Channel Id String>
channel_capacity: <Channel Capacity Tokens Number>
cltv_delta: <CLTV Delta Number>
fee_rate: <Fee Rate Number>
public_key: <Public Key Hex String>
}]
}
*/
module.exports = ({channels, end, ignore, mtokens, start}) => {
const distances = {};
const next = {};
const queue = PriorityQueue();
// Set all distances to Infinity, meaning the nodes cannot be reached
channels.forEach(({policies}) => {
return policies.forEach(policy => {
distances[policy.public_key] = {
distance: Infinity,
public_key: policy.public_key,
};
next[policy.public_key] = null;
return;
});
});
distances[end] = {
distance: BigInt(0),
fee: BigInt(0),
public_key: start,
receive: mtokens,
};
const checkEdge = ({channel, from, to}) => {
if (from !== start && !!channel.is_disabled) {
return;
}
const {id} = channel;
const mtokensToSend = BigInt(distances[to].receive);
if (BigInt(channel.capacity) * BigInt(mtokPerTok) < mtokensToSend) {
return;
}
const policy = channel.policies.find(n => n.public_key === from);
if (!!policy.is_disabled || policy.min_htlc_mtokens > mtokensToSend) {
return;
}
if (!!(ignore || []).find(n => !n.channel && n.public_key === from)) {
return;
}
if (!!(ignore || []).find(n => n.channel === id && n.public_key === to)) {
return;
}
const cltv = from === start ? minCltvDelta : policy.cltv_delta;
const hopFeeMtokens = BigInt(policyFee({policy, mtokens}).fee_mtokens);
const peer = channel.policies.find(n => n.public_key !== from);
const feeMtokens = from === start ? BigInt(minFee) : hopFeeMtokens;
const prospectiveDistance = distances[to].distance + feeMtokens;
if (prospectiveDistance >= distances[from].distance) {
return;
}
distances[from] = {
distance: prospectiveDistance,
fee_mtokens: feeMtokens,
public_key: from,
receive: mtokensToSend + feeMtokens,
};
next[from] = {
base_fee_mtokens: peer.base_fee_mtokens,
channel: channel.id,
channel_capacity: channel.capacity,
cltv_delta: peer.cltv_delta,
fee_rate: peer.fee_rate,
public_key: peer.public_key,
};
const priority = parseInt(prospectiveDistance.toString(), decBase);
queue.enqueue(from, priority + queueStart);
return;
};
const priority = parseInt(distances[end].distance.toString(), decBase);
queue.enqueue(end, priority + queueStart);
// Pull from the priority queue to check edges
while (!queue.isEmpty()) {
const bestNode = queue.dequeue();
if (bestNode === start) {
break;
}
channels
.filter(({policies}) => {
// Eliminate empty policies
if (!!policies.find(policy => !policy.public_key)) {
return false;
}
// Eliminate policies that aren't the best node
return !!policies.find(policy => policy.public_key === bestNode);
})
.forEach(channel => {
const policy = channel.policies.find(n => n.public_key !== bestNode);
// Check edge between the channel peers
return checkEdge({channel, from: policy.public_key, to: bestNode});
});
}
// Exit early when there is no path
if (!next[start]) {
return {};
}
let current = start;
const hops = [];
// Map hops to a series
while (current !== end) {
hops.push(next[current]);
current = next[current].public_key;
}
return {hops};
};