Skip to content

Commit c641549

Browse files
Fix computation of path weight (#1794)
There are two cases depending on whether you use weight ratios or not. They used to behave very differently: * Without weight ratios, the weight would be the total amount to send (base amount + fees) * With weight ratios, the ratios would apply to the total amount, not just the fees. The effect is that only the number of hops and then the weight factors matter as the fee itself is negligible compared to the total amount. The code is now shared and the weight is now the sum of the fees (multiplied by a factor if using weight ratios).
1 parent 55b50ec commit c641549

File tree

2 files changed

+33
-33
lines changed

2 files changed

+33
-33
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala

+26-26
Original file line numberDiff line numberDiff line change
@@ -264,32 +264,32 @@ object Graph {
264264
* @param currentBlockHeight the height of the chain tip (latest block).
265265
* @param weightRatios ratios used to 'weight' edges when searching for the shortest path
266266
*/
267-
private def addEdgeWeight(sender: PublicKey, edge: GraphEdge, prev: RichWeight, currentBlockHeight: Long, weightRatios: Option[WeightRatios]): RichWeight = weightRatios match {
268-
case None =>
269-
val totalCost = if (edge.desc.a == sender) prev.cost else addEdgeFees(edge, prev.cost)
270-
val totalCltv = if (edge.desc.a == sender) prev.cltv else prev.cltv + edge.update.cltvExpiryDelta
271-
RichWeight(totalCost, prev.length + 1, totalCltv, totalCost.toLong)
272-
case Some(wr) =>
273-
import RoutingHeuristics._
274-
275-
// Every edge is weighted by funding block height where older blocks add less weight, the window considered is 2 months.
276-
val channelBlockHeight = ShortChannelId.coordinates(edge.desc.shortChannelId).blockHeight
277-
val ageFactor = normalize(channelBlockHeight, min = currentBlockHeight - BLOCK_TIME_TWO_MONTHS, max = currentBlockHeight)
278-
279-
// Every edge is weighted by channel capacity, larger channels add less weight
280-
val edgeMaxCapacity = edge.capacity.toMilliSatoshi
281-
val capFactor = 1 - normalize(edgeMaxCapacity.toLong, CAPACITY_CHANNEL_LOW.toLong, CAPACITY_CHANNEL_HIGH.toLong)
282-
283-
// Every edge is weighted by its cltv-delta value, normalized
284-
val cltvFactor = normalize(edge.update.cltvExpiryDelta.toInt, CLTV_LOW, CLTV_HIGH)
285-
286-
val totalCost = if (edge.desc.a == sender) prev.cost else addEdgeFees(edge, prev.cost)
287-
val totalCltv = if (edge.desc.a == sender) prev.cltv else prev.cltv + edge.update.cltvExpiryDelta
288-
// NB we're guaranteed to have weightRatios and factors > 0
289-
val factor = (cltvFactor * wr.cltvDeltaFactor) + (ageFactor * wr.ageFactor) + (capFactor * wr.capacityFactor)
290-
val totalWeight = if (edge.desc.a == sender) prev.weight else prev.weight + totalCost.toLong * factor
291-
292-
RichWeight(totalCost, prev.length + 1, totalCltv, totalWeight)
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)
269+
val fee = totalCost - prev.cost
270+
val totalCltv = if (edge.desc.a == sender) prev.cltv else prev.cltv + edge.update.cltvExpiryDelta
271+
val factor = weightRatios match {
272+
case None =>
273+
1.0
274+
case Some(wr) =>
275+
import RoutingHeuristics._
276+
277+
// Every edge is weighted by funding block height where older blocks add less weight, the window considered is 2 months.
278+
val channelBlockHeight = ShortChannelId.coordinates(edge.desc.shortChannelId).blockHeight
279+
val ageFactor = normalize(channelBlockHeight, min = currentBlockHeight - BLOCK_TIME_TWO_MONTHS, max = currentBlockHeight)
280+
281+
// Every edge is weighted by channel capacity, larger channels add less weight
282+
val edgeMaxCapacity = edge.capacity.toMilliSatoshi
283+
val capFactor = 1 - normalize(edgeMaxCapacity.toLong, CAPACITY_CHANNEL_LOW.toLong, CAPACITY_CHANNEL_HIGH.toLong)
284+
285+
// Every edge is weighted by its cltv-delta value, normalized
286+
val cltvFactor = normalize(edge.update.cltvExpiryDelta.toInt, CLTV_LOW, CLTV_HIGH)
287+
288+
// NB we're guaranteed to have weightRatios and factors > 0
289+
(cltvFactor * wr.cltvDeltaFactor) + (ageFactor * wr.ageFactor) + (capFactor * wr.capacityFactor)
290+
}
291+
val totalWeight = prev.weight + fee.toLong * factor
292+
RichWeight(totalCost, prev.length + 1, totalCltv, totalWeight)
293293
}
294294

295295
/**

eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala

+7-7
Original file line numberDiff line numberDiff line change
@@ -807,13 +807,13 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution {
807807
// A -> E -> F -> D is 'timeout optimized', lower CLTV route (totFees = 3, totCltv = 18)
808808
// A -> E -> C -> D is 'capacity optimized', more recent channel/larger capacity route
809809
val g = DirectedGraph(List(
810-
makeEdge(1L, a, b, feeBase = 0 msat, 0, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(13)),
811-
makeEdge(4L, a, e, feeBase = 0 msat, 0, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(12)),
812-
makeEdge(2L, b, c, feeBase = 1 msat, 0, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(500)),
813-
makeEdge(3L, c, d, feeBase = 1 msat, 0, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(500)),
814-
makeEdge(5L, e, f, feeBase = 2 msat, 0, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(9)),
815-
makeEdge(6L, f, d, feeBase = 2 msat, 0, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(9)),
816-
makeEdge(7L, e, c, feeBase = 2 msat, 0, minHtlc = 0 msat, capacity = largeCapacity, cltvDelta = CltvExpiryDelta(12))
810+
makeEdge(1L, a, b, feeBase = 0 msat, 1000, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(13)),
811+
makeEdge(4L, a, e, feeBase = 0 msat, 1000, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(12)),
812+
makeEdge(2L, b, c, feeBase = 1 msat, 1000, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(500)),
813+
makeEdge(3L, c, d, feeBase = 1 msat, 1000, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(500)),
814+
makeEdge(5L, e, f, feeBase = 2 msat, 1000, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(9)),
815+
makeEdge(6L, f, d, feeBase = 2 msat, 1000, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(9)),
816+
makeEdge(7L, e, c, feeBase = 2 msat, 1000, minHtlc = 0 msat, capacity = largeCapacity, cltvDelta = CltvExpiryDelta(12))
817817
))
818818

819819
val Success(routeFeeOptimized :: Nil) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = 400000)

0 commit comments

Comments
 (0)