diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala index 85b98f012d..d1a8fa883f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala @@ -88,6 +88,8 @@ object Graph { override def compare(x: WeightedPath, y: WeightedPath): Int = y.weight.compare(x.weight) } + case class InfiniteLoop(path: Seq[GraphEdge]) extends Exception + /** * Yen's algorithm to find the k-shortest (loop-less) paths in a graph, uses dijkstra as search algo. Is guaranteed to * terminate finding at most @pathsToFind paths sorted by cost (the cheapest is in position 0). @@ -271,6 +273,9 @@ object Graph { while (current.isDefined) { edgePath += current.get current = bestEdges.get(current.get.desc.b) + if(edgePath.length > RouteCalculation.ROUTE_MAX_LENGTH){ + throw InfiniteLoop(edgePath.toSeq) + } } edgePath.toSeq } else { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala index 67c5311e55..0f1905c4e6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair._ import fr.acinq.eclair.payment.PaymentRequest.ExtraHop import fr.acinq.eclair.router.Graph.GraphStructure.DirectedGraph.graphEdgeToHop import fr.acinq.eclair.router.Graph.GraphStructure.{DirectedGraph, GraphEdge} -import fr.acinq.eclair.router.Graph.{RichWeight, RoutingHeuristics} +import fr.acinq.eclair.router.Graph.{InfiniteLoop, RichWeight, RoutingHeuristics} import fr.acinq.eclair.router.Monitoring.{Metrics, Tags} import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.wire.protocol.ChannelUpdate @@ -117,7 +117,7 @@ object RouteCalculation { val routesToFind = if (params.randomize) DEFAULT_ROUTES_COUNT else 1 log.info(s"finding routes ${r.source}->${r.target} with assistedChannels={} ignoreNodes={} ignoreChannels={} excludedChannels={}", assistedChannels.keys.mkString(","), r.ignore.nodes.map(_.value).mkString(","), r.ignore.channels.mkString(","), d.excludedChannels.mkString(",")) - log.info("finding routes with randomize={} params={}", params.randomize, params) + log.info("finding routes with params={}, multiPart={}", params, r.allowMultiPart) val tags = TagSet.Empty.withTag(Tags.MultiPart, r.allowMultiPart).withTag(Tags.Amount, Tags.amountBucket(r.amount)) KamonExt.time(Metrics.FindRouteDuration.withTags(tags.withTag(Tags.NumberOfRoutes, routesToFind.toLong))) { val result = if (r.allowMultiPart) { @@ -130,6 +130,10 @@ object RouteCalculation { Metrics.RouteResults.withTags(tags).record(routes.length) routes.foreach(route => Metrics.RouteLength.withTags(tags).record(route.length)) ctx.sender() ! RouteResponse(routes) + case Failure(InfiniteLoop(loop)) => + log.error(s"found infinite loop ${loop.map(edge => edge.desc).mkString(" -> ")}") + Metrics.FindRouteErrors.withTags(tags.withTag(Tags.Error, "InfiniteLoop")).increment() + ctx.sender() ! Status.Failure(InfiniteLoop(loop)) case Failure(t) => val failure = if (isNeighborBalanceTooLow(d.graph, r)) BalanceTooLow else t Metrics.FindRouteErrors.withTags(tags.withTag(Tags.Error, failure.getClass.getSimpleName)).increment()