@@ -73,17 +73,18 @@ object Graph {
73
73
* Yen's algorithm to find the k-shortest (loop-less) paths in a graph, uses dijkstra as search algo. Is guaranteed to
74
74
* terminate finding at most @pathsToFind paths sorted by cost (the cheapest is in position 0).
75
75
*
76
- * @param graph the graph on which will be performed the search
77
- * @param sourceNode the starting node of the path we're looking for (payer)
78
- * @param targetNode the destination node of the path (recipient)
79
- * @param amount amount to send to the last node
80
- * @param ignoredEdges channels that should be avoided
81
- * @param ignoredVertices nodes that should be avoided
82
- * @param extraEdges additional edges that can be used (e.g. private channels from invoices)
83
- * @param pathsToFind number of distinct paths to be returned
84
- * @param wr ratios used to 'weight' edges when searching for the shortest path
85
- * @param currentBlockHeight the height of the chain tip (latest block)
86
- * @param boundaries a predicate function that can be used to impose limits on the outcome of the search
76
+ * @param graph the graph on which will be performed the search
77
+ * @param sourceNode the starting node of the path we're looking for (payer)
78
+ * @param targetNode the destination node of the path (recipient)
79
+ * @param amount amount to send to the last node
80
+ * @param ignoredEdges channels that should be avoided
81
+ * @param ignoredVertices nodes that should be avoided
82
+ * @param extraEdges additional edges that can be used (e.g. private channels from invoices)
83
+ * @param pathsToFind number of distinct paths to be returned
84
+ * @param wr ratios used to 'weight' edges when searching for the shortest path
85
+ * @param currentBlockHeight the height of the chain tip (latest block)
86
+ * @param boundaries a predicate function that can be used to impose limits on the outcome of the search
87
+ * @param includeLocalChannelCost if the path is for relaying and we need to include the cost of the local channel
87
88
*/
88
89
def yenKshortestPaths (graph : DirectedGraph ,
89
90
sourceNode : PublicKey ,
@@ -95,10 +96,11 @@ object Graph {
95
96
pathsToFind : Int ,
96
97
wr : Option [WeightRatios ],
97
98
currentBlockHeight : Long ,
98
- boundaries : RichWeight => Boolean ): Seq [WeightedPath ] = {
99
+ boundaries : RichWeight => Boolean ,
100
+ includeLocalChannelCost : Boolean ): Seq [WeightedPath ] = {
99
101
// find the shortest path (k = 0)
100
102
val targetWeight = RichWeight (amount, 0 , CltvExpiryDelta (0 ), 0 )
101
- val shortestPath = dijkstraShortestPath(graph, sourceNode, targetNode, ignoredEdges, ignoredVertices, extraEdges, targetWeight, boundaries, currentBlockHeight, wr)
103
+ val shortestPath = dijkstraShortestPath(graph, sourceNode, targetNode, ignoredEdges, ignoredVertices, extraEdges, targetWeight, boundaries, currentBlockHeight, wr, includeLocalChannelCost )
102
104
if (shortestPath.isEmpty) {
103
105
return Seq .empty // if we can't even find a single path, avoid returning a Seq(Seq.empty)
104
106
}
@@ -110,7 +112,7 @@ object Graph {
110
112
111
113
var allSpurPathsFound = false
112
114
val shortestPaths = new mutable.Queue [PathWithSpur ]
113
- shortestPaths.enqueue(PathWithSpur (WeightedPath (shortestPath, pathWeight(sourceNode, shortestPath, amount, currentBlockHeight, wr)), 0 ))
115
+ shortestPaths.enqueue(PathWithSpur (WeightedPath (shortestPath, pathWeight(sourceNode, shortestPath, amount, currentBlockHeight, wr, includeLocalChannelCost )), 0 ))
114
116
// stores the candidates for the k-th shortest path, sorted by path cost
115
117
val candidates = new mutable.PriorityQueue [PathWithSpur ]
116
118
@@ -135,12 +137,12 @@ object Graph {
135
137
val alreadyExploredEdges = shortestPaths.collect { case p if p.p.path.takeRight(i) == rootPathEdges => p.p.path(p.p.path.length - 1 - i).desc }.toSet
136
138
// we also want to ignore any vertex on the root path to prevent loops
137
139
val alreadyExploredVertices = rootPathEdges.map(_.desc.b).toSet
138
- val rootPathWeight = pathWeight(sourceNode, rootPathEdges, amount, currentBlockHeight, wr)
140
+ val rootPathWeight = pathWeight(sourceNode, rootPathEdges, amount, currentBlockHeight, wr, includeLocalChannelCost )
139
141
// find the "spur" path, a sub-path going from the spur node to the target avoiding previously found sub-paths
140
- val spurPath = dijkstraShortestPath(graph, sourceNode, spurNode, ignoredEdges ++ alreadyExploredEdges, ignoredVertices ++ alreadyExploredVertices, extraEdges, rootPathWeight, boundaries, currentBlockHeight, wr)
142
+ val spurPath = dijkstraShortestPath(graph, sourceNode, spurNode, ignoredEdges ++ alreadyExploredEdges, ignoredVertices ++ alreadyExploredVertices, extraEdges, rootPathWeight, boundaries, currentBlockHeight, wr, includeLocalChannelCost )
141
143
if (spurPath.nonEmpty) {
142
144
val completePath = spurPath ++ rootPathEdges
143
- val candidatePath = WeightedPath (completePath, pathWeight(sourceNode, completePath, amount, currentBlockHeight, wr))
145
+ val candidatePath = WeightedPath (completePath, pathWeight(sourceNode, completePath, amount, currentBlockHeight, wr, includeLocalChannelCost ))
144
146
candidates.enqueue(PathWithSpur (candidatePath, i))
145
147
}
146
148
}
@@ -163,16 +165,17 @@ object Graph {
163
165
* path from the target to the source (this is because we want to calculate the weight of the edges correctly). The
164
166
* graph @param g is optimized for querying the incoming edges given a vertex.
165
167
*
166
- * @param g the graph on which will be performed the search
167
- * @param sourceNode the starting node of the path we're looking for (payer)
168
- * @param targetNode the destination node of the path
169
- * @param ignoredEdges channels that should be avoided
170
- * @param ignoredVertices nodes that should be avoided
171
- * @param extraEdges additional edges that can be used (e.g. private channels from invoices)
172
- * @param initialWeight weight that will be applied to the target node
173
- * @param boundaries a predicate function that can be used to impose limits on the outcome of the search
174
- * @param currentBlockHeight the height of the chain tip (latest block)
175
- * @param wr ratios used to 'weight' edges when searching for the shortest path
168
+ * @param g the graph on which will be performed the search
169
+ * @param sourceNode the starting node of the path we're looking for (payer)
170
+ * @param targetNode the destination node of the path
171
+ * @param ignoredEdges channels that should be avoided
172
+ * @param ignoredVertices nodes that should be avoided
173
+ * @param extraEdges additional edges that can be used (e.g. private channels from invoices)
174
+ * @param initialWeight weight that will be applied to the target node
175
+ * @param boundaries a predicate function that can be used to impose limits on the outcome of the search
176
+ * @param currentBlockHeight the height of the chain tip (latest block)
177
+ * @param wr ratios used to 'weight' edges when searching for the shortest path
178
+ * @param includeLocalChannelCost if the path is for relaying and we need to include the cost of the local channel
176
179
*/
177
180
private def dijkstraShortestPath (g : DirectedGraph ,
178
181
sourceNode : PublicKey ,
@@ -183,7 +186,8 @@ object Graph {
183
186
initialWeight : RichWeight ,
184
187
boundaries : RichWeight => Boolean ,
185
188
currentBlockHeight : Long ,
186
- wr : Option [WeightRatios ]): Seq [GraphEdge ] = {
189
+ wr : Option [WeightRatios ],
190
+ includeLocalChannelCost : Boolean ): Seq [GraphEdge ] = {
187
191
// the graph does not contain source/destination nodes
188
192
val sourceNotInGraph = ! g.containsVertex(sourceNode) && ! extraEdges.exists(_.desc.a == sourceNode)
189
193
val targetNotInGraph = ! g.containsVertex(targetNode) && ! extraEdges.exists(_.desc.b == targetNode)
@@ -221,7 +225,7 @@ object Graph {
221
225
val neighbor = edge.desc.a
222
226
// NB: this contains the amount (including fees) that will need to be sent to `neighbor`, but the amount that
223
227
// will be relayed through that edge is the one in `currentWeight`.
224
- val neighborWeight = addEdgeWeight(sourceNode, edge, current.weight, currentBlockHeight, wr)
228
+ val neighborWeight = addEdgeWeight(sourceNode, edge, current.weight, currentBlockHeight, wr, includeLocalChannelCost )
225
229
val canRelayAmount = current.weight.cost <= edge.capacity &&
226
230
edge.balance_opt.forall(current.weight.cost <= _) &&
227
231
edge.update.htlcMaximumMsat.forall(current.weight.cost <= _) &&
@@ -258,16 +262,17 @@ object Graph {
258
262
/**
259
263
* Add the given edge to the path and compute the new weight.
260
264
*
261
- * @param sender node sending the payment
262
- * @param edge the edge we want to cross
263
- * @param prev weight of the rest of the path
264
- * @param currentBlockHeight the height of the chain tip (latest block).
265
- * @param weightRatios ratios used to 'weight' edges when searching for the shortest path
265
+ * @param sender node sending the payment
266
+ * @param edge the edge we want to cross
267
+ * @param prev weight of the rest of the path
268
+ * @param currentBlockHeight the height of the chain tip (latest block).
269
+ * @param weightRatios ratios used to 'weight' edges when searching for the shortest path
270
+ * @param includeLocalChannelCost if the path is for relaying and we need to include the cost of the local channel
266
271
*/
267
- private def addEdgeWeight (sender : PublicKey , edge : GraphEdge , prev : RichWeight , currentBlockHeight : Long , weightRatios : Option [WeightRatios ]): RichWeight = {
268
- val totalCost = if (edge.desc.a == sender) prev.cost else addEdgeFees(edge, prev.cost)
272
+ private def addEdgeWeight (sender : PublicKey , edge : GraphEdge , prev : RichWeight , currentBlockHeight : Long , weightRatios : Option [WeightRatios ], includeLocalChannelCost : Boolean ): RichWeight = {
273
+ val totalCost = if (edge.desc.a == sender && ! includeLocalChannelCost ) prev.cost else addEdgeFees(edge, prev.cost)
269
274
val fee = totalCost - prev.cost
270
- val totalCltv = if (edge.desc.a == sender) prev.cltv else prev.cltv + edge.update.cltvExpiryDelta
275
+ val totalCltv = if (edge.desc.a == sender && ! includeLocalChannelCost ) prev.cltv else prev.cltv + edge.update.cltvExpiryDelta
271
276
val factor = weightRatios match {
272
277
case None =>
273
278
1.0
@@ -322,15 +327,16 @@ object Graph {
322
327
* Calculates the total weighted cost of a path.
323
328
* Note that the first hop from the sender is ignored: we don't pay a routing fee to ourselves.
324
329
*
325
- * @param sender node sending the payment
326
- * @param path candidate path.
327
- * @param amount amount to send to the last node.
328
- * @param currentBlockHeight the height of the chain tip (latest block).
329
- * @param wr ratios used to 'weight' edges when searching for the shortest path
330
+ * @param sender node sending the payment
331
+ * @param path candidate path.
332
+ * @param amount amount to send to the last node.
333
+ * @param currentBlockHeight the height of the chain tip (latest block).
334
+ * @param wr ratios used to 'weight' edges when searching for the shortest path
335
+ * @param includeLocalChannelCost if the path is for relaying and we need to include the cost of the local channel
330
336
*/
331
- def pathWeight (sender : PublicKey , path : Seq [GraphEdge ], amount : MilliSatoshi , currentBlockHeight : Long , wr : Option [WeightRatios ]): RichWeight = {
337
+ def pathWeight (sender : PublicKey , path : Seq [GraphEdge ], amount : MilliSatoshi , currentBlockHeight : Long , wr : Option [WeightRatios ], includeLocalChannelCost : Boolean ): RichWeight = {
332
338
path.foldRight(RichWeight (amount, 0 , CltvExpiryDelta (0 ), 0 )) { (edge, prev) =>
333
- addEdgeWeight(sender, edge, prev, currentBlockHeight, wr)
339
+ addEdgeWeight(sender, edge, prev, currentBlockHeight, wr, includeLocalChannelCost )
334
340
}
335
341
}
336
342
0 commit comments