From 653fbc5eb453767946eeefff3fbc5ec6fda4f724 Mon Sep 17 00:00:00 2001 From: Thomas HUET Date: Wed, 27 Oct 2021 15:47:57 +0200 Subject: [PATCH 1/2] Use balance estimates from past payments in path-finding --- eclair-core/src/main/resources/reference.conf | 2 + .../scala/fr/acinq/eclair/NodeParams.scala | 1 + .../remote/EclairInternalsSerializer.scala | 3 +- .../acinq/eclair/router/BalanceEstimate.scala | 13 +- .../scala/fr/acinq/eclair/router/Graph.scala | 46 +-- .../eclair/router/RouteCalculation.scala | 14 +- .../eclair/router/BalanceEstimateSpec.scala | 18 +- .../fr/acinq/eclair/router/GraphSpec.scala | 25 +- .../eclair/router/RouteCalculationSpec.scala | 311 +++++++++--------- 9 files changed, 222 insertions(+), 211 deletions(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 82fe9de1d5..bfb081f647 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -391,6 +391,8 @@ eclair { // probability of success, however is penalizes less the paths with a low probability of success. use-log-probability = false + use-past-relay-data = false + mpp { min-amount-satoshis = 15000 // minimum amount sent via partial HTLCs max-parts = 5 // maximum number of HTLCs sent per payment: increasing this value will impact performance diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index 94cca6734d..caa0f6258b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -424,6 +424,7 @@ object NodeParams extends Logging { failureCost = getRelayFees(config.getConfig("failure-cost")), hopCost = getRelayFees(config.getConfig("hop-cost")), useLogProbability = config.getBoolean("use-log-probability"), + usePastRelaysData = config.getBoolean("use-past-relay-data"), )) }, mpp = MultiPartParams( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala index 01b634a0f4..1ca937057a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala @@ -68,7 +68,8 @@ object EclairInternalsSerializer { ("lockedFundsRisk" | double) :: ("failureCost" | relayFeesCodec) :: ("hopCost" | relayFeesCodec) :: - ("useLogProbability" | bool(8))).as[HeuristicsConstants] + ("useLogProbability" | bool(8)) :: + ("usePastRelaysData" | bool(8))).as[HeuristicsConstants] val multiPartParamsCodec: Codec[MultiPartParams] = ( ("minPartAmount" | millisatoshi) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/BalanceEstimate.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/BalanceEstimate.scala index 6aa56163bd..d9a90e6933 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/BalanceEstimate.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/BalanceEstimate.scala @@ -23,7 +23,7 @@ import fr.acinq.eclair.router.Router.{ChannelDesc, ChannelHop, Route} import fr.acinq.eclair.wire.protocol.NodeAnnouncement import fr.acinq.eclair.{MilliSatoshi, MilliSatoshiLong, ShortChannelId, TimestampSecond, TimestampSecondLong, ToMilliSatoshiConversion} -import scala.concurrent.duration.{DurationInt, FiniteDuration} +import scala.concurrent.duration.FiniteDuration /** * Estimates the balance between a pair of nodes @@ -235,6 +235,8 @@ object BalanceEstimate { case class BalancesEstimates(balances: Map[(PublicKey, PublicKey), BalanceEstimate], defaultHalfLife: FiniteDuration) { private def get(a: PublicKey, b: PublicKey): Option[BalanceEstimate] = balances.get((a, b)) + def get(edge: GraphEdge): BalanceEstimate = get(edge.desc.a, edge.desc.b).getOrElse(BalanceEstimate.empty(defaultHalfLife).addEdge(edge)) + def addEdge(edge: GraphEdge): BalancesEstimates = BalancesEstimates( balances.updatedWith((edge.desc.a, edge.desc.b))(balance => Some(balance.getOrElse(BalanceEstimate.empty(defaultHalfLife)).addEdge(edge)) @@ -284,7 +286,7 @@ case class BalancesEstimates(balances: Map[(PublicKey, PublicKey), BalanceEstima } -case class GraphWithBalanceEstimates(graph: DirectedGraph, private val balances: BalancesEstimates) { +case class GraphWithBalanceEstimates(graph: DirectedGraph, balances: BalancesEstimates) { def addOrUpdateVertex(ann: NodeAnnouncement): GraphWithBalanceEstimates = GraphWithBalanceEstimates(graph.addOrUpdateVertex(ann), balances) def addEdge(edge: GraphEdge): GraphWithBalanceEstimates = GraphWithBalanceEstimates(graph.addEdge(edge), balances.addEdge(edge)) @@ -317,13 +319,6 @@ case class GraphWithBalanceEstimates(graph: DirectedGraph, private val balances: def channelCouldNotSend(hop: ChannelHop, amount: MilliSatoshi): GraphWithBalanceEstimates = { GraphWithBalanceEstimates(graph, balances.channelCouldNotSend(hop, amount)) } - - def canSend(amount: MilliSatoshi, edge: GraphEdge): Double = { - balances.balances.get((edge.desc.a, edge.desc.b)) match { - case Some(estimate) => estimate.canSend(amount) - case None => BalanceEstimate.empty(1 hour).addEdge(edge).canSend(amount) - } - } } object GraphWithBalanceEstimates { 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 89af943807..c44b1f1082 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 @@ -61,11 +61,12 @@ object Graph { * The fee for a failed attempt and the fee per hop are never actually spent, they are used to incentivize shorter * paths or path with higher success probability. * - * @param lockedFundsRisk cost of having funds locked in htlc in msat per msat per block - * @param failureCost fee for a failed attempt - * @param hopCost virtual fee per hop (how much we're willing to pay to make the route one hop shorter) + * @param lockedFundsRisk cost of having funds locked in htlc in msat per msat per block + * @param failureCost fee for a failed attempt + * @param hopCost virtual fee per hop (how much we're willing to pay to make the route one hop shorter) + * @param usePastRelaysData use data from past relays to estimate the balance of the channels */ - case class HeuristicsConstants(lockedFundsRisk: Double, failureCost: RelayFees, hopCost: RelayFees, useLogProbability: Boolean) + case class HeuristicsConstants(lockedFundsRisk: Double, failureCost: RelayFees, hopCost: RelayFees, useLogProbability: Boolean, usePastRelaysData: Boolean) case class WeightedNode(key: PublicKey, weight: RichWeight) @@ -109,7 +110,7 @@ object Graph { * @param boundaries a predicate function that can be used to impose limits on the outcome of the search * @param includeLocalChannelCost if the path is for relaying and we need to include the cost of the local channel */ - def yenKshortestPaths(graph: DirectedGraph, + def yenKshortestPaths(g: GraphWithBalanceEstimates, sourceNode: PublicKey, targetNode: PublicKey, amount: MilliSatoshi, @@ -123,7 +124,7 @@ object Graph { includeLocalChannelCost: Boolean): Seq[WeightedPath] = { // find the shortest path (k = 0) val targetWeight = RichWeight(amount, 0, CltvExpiryDelta(0), 1.0, 0 msat, 0 msat, 0.0) - val shortestPath = dijkstraShortestPath(graph, sourceNode, targetNode, ignoredEdges, ignoredVertices, extraEdges, targetWeight, boundaries, currentBlockHeight, wr, includeLocalChannelCost) + val shortestPath = dijkstraShortestPath(g, sourceNode, targetNode, ignoredEdges, ignoredVertices, extraEdges, targetWeight, boundaries, currentBlockHeight, wr, includeLocalChannelCost) if (shortestPath.isEmpty) { return Seq.empty // if we can't even find a single path, avoid returning a Seq(Seq.empty) } @@ -135,7 +136,7 @@ object Graph { var allSpurPathsFound = false val shortestPaths = new mutable.Queue[PathWithSpur] - shortestPaths.enqueue(PathWithSpur(WeightedPath(shortestPath, pathWeight(sourceNode, shortestPath, amount, currentBlockHeight, wr, includeLocalChannelCost)), 0)) + shortestPaths.enqueue(PathWithSpur(WeightedPath(shortestPath, pathWeight(g.balances, sourceNode, shortestPath, amount, currentBlockHeight, wr, includeLocalChannelCost)), 0)) // stores the candidates for the k-th shortest path, sorted by path cost val candidates = new mutable.PriorityQueue[PathWithSpur] @@ -160,12 +161,12 @@ object Graph { val alreadyExploredEdges = shortestPaths.collect { case p if p.p.path.takeRight(i) == rootPathEdges => p.p.path(p.p.path.length - 1 - i).desc }.toSet // we also want to ignore any vertex on the root path to prevent loops val alreadyExploredVertices = rootPathEdges.map(_.desc.b).toSet - val rootPathWeight = pathWeight(sourceNode, rootPathEdges, amount, currentBlockHeight, wr, includeLocalChannelCost) + val rootPathWeight = pathWeight(g.balances, sourceNode, rootPathEdges, amount, currentBlockHeight, wr, includeLocalChannelCost) // find the "spur" path, a sub-path going from the spur node to the target avoiding previously found sub-paths - val spurPath = dijkstraShortestPath(graph, sourceNode, spurNode, ignoredEdges ++ alreadyExploredEdges, ignoredVertices ++ alreadyExploredVertices, extraEdges, rootPathWeight, boundaries, currentBlockHeight, wr, includeLocalChannelCost) + val spurPath = dijkstraShortestPath(g, sourceNode, spurNode, ignoredEdges ++ alreadyExploredEdges, ignoredVertices ++ alreadyExploredVertices, extraEdges, rootPathWeight, boundaries, currentBlockHeight, wr, includeLocalChannelCost) if (spurPath.nonEmpty) { val completePath = spurPath ++ rootPathEdges - val candidatePath = WeightedPath(completePath, pathWeight(sourceNode, completePath, amount, currentBlockHeight, wr, includeLocalChannelCost)) + val candidatePath = WeightedPath(completePath, pathWeight(g.balances, sourceNode, completePath, amount, currentBlockHeight, wr, includeLocalChannelCost)) candidates.enqueue(PathWithSpur(candidatePath, i)) } } @@ -200,7 +201,7 @@ object Graph { * @param wr ratios used to 'weight' edges when searching for the shortest path * @param includeLocalChannelCost if the path is for relaying and we need to include the cost of the local channel */ - private def dijkstraShortestPath(g: DirectedGraph, + private def dijkstraShortestPath(g: GraphWithBalanceEstimates, sourceNode: PublicKey, targetNode: PublicKey, ignoredEdges: Set[ChannelDesc], @@ -212,8 +213,8 @@ object Graph { wr: Either[WeightRatios, HeuristicsConstants], includeLocalChannelCost: Boolean): Seq[GraphEdge] = { // the graph does not contain source/destination nodes - val sourceNotInGraph = !g.containsVertex(sourceNode) && !extraEdges.exists(_.desc.a == sourceNode) - val targetNotInGraph = !g.containsVertex(targetNode) && !extraEdges.exists(_.desc.b == targetNode) + val sourceNotInGraph = !g.graph.containsVertex(sourceNode) && !extraEdges.exists(_.desc.a == sourceNode) + val targetNotInGraph = !g.graph.containsVertex(targetNode) && !extraEdges.exists(_.desc.b == targetNode) if (sourceNotInGraph || targetNotInGraph) { return Seq.empty } @@ -242,7 +243,7 @@ object Graph { val neighborEdges = { val extraNeighbors = extraEdges.filter(_.desc.b == current.key) // the resulting set must have only one element per shortChannelId; we prioritize extra edges - g.getIncomingEdgesOf(current.key).collect{case e: GraphEdge if !extraNeighbors.exists(_.desc.shortChannelId == e.desc.shortChannelId) => e} ++ extraNeighbors + g.graph.getIncomingEdgesOf(current.key).collect{case e: GraphEdge if !extraNeighbors.exists(_.desc.shortChannelId == e.desc.shortChannelId) => e} ++ extraNeighbors } neighborEdges.foreach { edge => val neighbor = edge.desc.a @@ -254,7 +255,7 @@ object Graph { !ignoredVertices.contains(neighbor)) { // NB: this contains the amount (including fees) that will need to be sent to `neighbor`, but the amount that // will be relayed through that edge is the one in `currentWeight`. - val neighborWeight = addEdgeWeight(sourceNode, edge, current.weight, currentBlockHeight, wr, includeLocalChannelCost) + val neighborWeight = addEdgeWeight(sourceNode, edge, g.balances.get(edge), current.weight, currentBlockHeight, wr, includeLocalChannelCost) if (boundaries(neighborWeight)) { val previousNeighborWeight = bestWeights.getOrElse(neighbor, RichWeight(MilliSatoshi(Long.MaxValue), Int.MaxValue, CltvExpiryDelta(Int.MaxValue), 0.0, MilliSatoshi(Long.MaxValue), MilliSatoshi(Long.MaxValue), Double.MaxValue)) // if this path between neighbor and the target has a shorter distance than previously known, we select it @@ -298,7 +299,7 @@ object Graph { * @param weightRatios ratios used to 'weight' edges when searching for the shortest path * @param includeLocalChannelCost if the path is for relaying and we need to include the cost of the local channel */ - private def addEdgeWeight(sender: PublicKey, edge: GraphEdge, prev: RichWeight, currentBlockHeight: BlockHeight, weightRatios: Either[WeightRatios, HeuristicsConstants], includeLocalChannelCost: Boolean): RichWeight = { + private def addEdgeWeight(sender: PublicKey, edge: GraphEdge, balance: BalanceEstimate, prev: RichWeight, currentBlockHeight: BlockHeight, weightRatios: Either[WeightRatios, HeuristicsConstants], includeLocalChannelCost: Boolean): RichWeight = { val totalAmount = if (edge.desc.a == sender && !includeLocalChannelCost) prev.amount else addEdgeFees(edge, prev.amount) val fee = totalAmount - prev.amount val totalFees = prev.fees + fee @@ -335,7 +336,14 @@ object Graph { val hopCost = nodeFee(heuristicsConstants.hopCost, prev.amount) val totalHopsCost = prev.virtualFees + hopCost // If we know the balance of the channel, then we will check separately that it can relay the payment. - val successProbability = if (edge.balance_opt.nonEmpty) 1.0 else 1.0 - prev.amount.toLong.toDouble / edge.capacity.toMilliSatoshi.toLong.toDouble + val successProbability = + if (edge.balance_opt.nonEmpty){ + 1.0 + } else if (heuristicsConstants.usePastRelaysData) { + balance.canSend(prev.amount) + } else { + 1.0 - prev.amount.toLong.toDouble / edge.capacity.toMilliSatoshi.toLong.toDouble + } if (successProbability < 0) { throw NegativeProbability(edge, prev, heuristicsConstants) } @@ -396,9 +404,9 @@ object Graph { * @param wr ratios used to 'weight' edges when searching for the shortest path * @param includeLocalChannelCost if the path is for relaying and we need to include the cost of the local channel */ - def pathWeight(sender: PublicKey, path: Seq[GraphEdge], amount: MilliSatoshi, currentBlockHeight: BlockHeight, wr: Either[WeightRatios, HeuristicsConstants], includeLocalChannelCost: Boolean): RichWeight = { + def pathWeight(balances: BalancesEstimates, sender: PublicKey, path: Seq[GraphEdge], amount: MilliSatoshi, currentBlockHeight: BlockHeight, wr: Either[WeightRatios, HeuristicsConstants], includeLocalChannelCost: Boolean): RichWeight = { path.foldRight(RichWeight(amount, 0, CltvExpiryDelta(0), 1.0, 0 msat, 0 msat, 0.0)) { (edge, prev) => - addEdgeWeight(sender, edge, prev, currentBlockHeight, wr, includeLocalChannelCost) + addEdgeWeight(sender, edge, balances.get(edge), prev, currentBlockHeight, wr, includeLocalChannelCost) } } 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 b0b1e4c4c9..330271320b 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 @@ -209,9 +209,9 @@ object RouteCalculation { val tags = TagSet.Empty.withTag(Tags.MultiPart, r.allowMultiPart).withTag(Tags.Amount, Tags.amountBucket(amountToSend)) KamonExt.time(Metrics.FindRouteDuration.withTags(tags.withTag(Tags.NumberOfRoutes, routesToFind.toLong))) { val result = if (r.allowMultiPart) { - findMultiPartRoute(d.graphWithBalances.graph, r.source, targetNodeId, amountToSend, maxFee, extraEdges, ignoredEdges, r.ignore.nodes, r.pendingPayments, r.routeParams, currentBlockHeight) + findMultiPartRoute(d.graphWithBalances, r.source, targetNodeId, amountToSend, maxFee, extraEdges, ignoredEdges, r.ignore.nodes, r.pendingPayments, r.routeParams, currentBlockHeight) } else { - findRoute(d.graphWithBalances.graph, r.source, targetNodeId, amountToSend, maxFee, routesToFind, extraEdges, ignoredEdges, r.ignore.nodes, r.routeParams, currentBlockHeight) + findRoute(d.graphWithBalances, r.source, targetNodeId, amountToSend, maxFee, routesToFind, extraEdges, ignoredEdges, r.ignore.nodes, r.routeParams, currentBlockHeight) } result.map(routes => addFinalHop(r.target, routes)) match { case Success(routes) => @@ -294,7 +294,7 @@ object RouteCalculation { * @param routeParams a set of parameters that can restrict the route search * @return the computed routes to the destination @param targetNodeId */ - def findRoute(g: DirectedGraph, + def findRoute(g: GraphWithBalanceEstimates, localNodeId: PublicKey, targetNodeId: PublicKey, amount: MilliSatoshi, @@ -312,7 +312,7 @@ object RouteCalculation { } @tailrec - private def findRouteInternal(g: DirectedGraph, + private def findRouteInternal(g: GraphWithBalanceEstimates, localNodeId: PublicKey, targetNodeId: PublicKey, amount: MilliSatoshi, @@ -370,7 +370,7 @@ object RouteCalculation { * @param routeParams a set of parameters that can restrict the route search * @return a set of disjoint routes to the destination @param targetNodeId with the payment amount split between them */ - def findMultiPartRoute(g: DirectedGraph, + def findMultiPartRoute(g: GraphWithBalanceEstimates, localNodeId: PublicKey, targetNodeId: PublicKey, amount: MilliSatoshi, @@ -394,7 +394,7 @@ object RouteCalculation { } } - private def findMultiPartRouteInternal(g: DirectedGraph, + private def findMultiPartRouteInternal(g: GraphWithBalanceEstimates, localNodeId: PublicKey, targetNodeId: PublicKey, amount: MilliSatoshi, @@ -409,7 +409,7 @@ object RouteCalculation { // When the recipient is a direct peer, we have complete visibility on our local channels so we can use more accurate MPP parameters. val routeParams1 = { case class DirectChannel(balance: MilliSatoshi, isEmpty: Boolean) - val directChannels = g.getEdgesBetween(localNodeId, targetNodeId).collect { + val directChannels = g.graph.getEdgesBetween(localNodeId, targetNodeId).collect { // We should always have balance information available for local channels. // NB: htlcMinimumMsat is set by our peer and may be 0 msat (even though it's not recommended). case GraphEdge(_, params, _, Some(balance)) => DirectChannel(balance, balance <= 0.msat || balance < params.htlcMinimum) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/BalanceEstimateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/BalanceEstimateSpec.scala index 9784f93345..e9a6d38567 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/BalanceEstimateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/BalanceEstimateSpec.scala @@ -277,16 +277,16 @@ class BalanceEstimateSpec extends AnyFunSuite { val edge_ab = makeEdge(a, b, 1, 10 sat) val edge_ba = makeEdge(b, a, 1, 10 sat) val edge_bc = makeEdge(b, c, 6, 10 sat) - assert(graphWithBalances.canSend(27500 msat, edge_ab) === 0.75 +- 0.01) - assert(graphWithBalances.canSend(55000 msat, edge_ab) === 0.5 +- 0.01) - assert(graphWithBalances.canSend(30000 msat, edge_ba) === 0.75 +- 0.01) - assert(graphWithBalances.canSend(60000 msat, edge_ba) === 0.5 +- 0.01) - assert(graphWithBalances.canSend(75000 msat, edge_bc) === 0.5 +- 0.01) - assert(graphWithBalances.canSend(100000 msat, edge_bc) === 0.33 +- 0.01) + assert(graphWithBalances.balances.get(edge_ab).canSend(27500 msat) === 0.75 +- 0.01) + assert(graphWithBalances.balances.get(edge_ab).canSend(55000 msat) === 0.5 +- 0.01) + assert(graphWithBalances.balances.get(edge_ba).canSend(30000 msat) === 0.75 +- 0.01) + assert(graphWithBalances.balances.get(edge_ba).canSend(60000 msat) === 0.5 +- 0.01) + assert(graphWithBalances.balances.get(edge_bc).canSend(75000 msat) === 0.5 +- 0.01) + assert(graphWithBalances.balances.get(edge_bc).canSend(100000 msat) === 0.33 +- 0.01) val unknownEdge = makeEdge(42, 40 sat) - assert(graphWithBalances.canSend(10000 msat, unknownEdge) === 0.75 +- 0.01) - assert(graphWithBalances.canSend(20000 msat, unknownEdge) === 0.5 +- 0.01) - assert(graphWithBalances.canSend(30000 msat, unknownEdge) === 0.25 +- 0.01) + assert(graphWithBalances.balances.get(unknownEdge).canSend(10000 msat) === 0.75 +- 0.01) + assert(graphWithBalances.balances.get(unknownEdge).canSend(20000 msat) === 0.5 +- 0.01) + assert(graphWithBalances.balances.get(unknownEdge).canSend(30000 msat) === 0.25 +- 0.01) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/GraphSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/GraphSpec.scala index a5ce621282..5ef7383772 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/GraphSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/GraphSpec.scala @@ -17,20 +17,19 @@ package fr.acinq.eclair.router import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey -import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, SatoshiLong} +import fr.acinq.bitcoin.scalacompat.SatoshiLong import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.router.Announcements.makeNodeAnnouncement -import fr.acinq.eclair.router.Graph.GraphStructure.{GraphEdge, DirectedGraph} +import fr.acinq.eclair.router.Graph.GraphStructure.{DirectedGraph, GraphEdge} import fr.acinq.eclair.router.Graph.{HeuristicsConstants, MessagePath, WeightRatios, yenKshortestPaths} import fr.acinq.eclair.router.RouteCalculationSpec._ -import fr.acinq.eclair.router.Router.{ChannelDesc, PublicChannel} -import fr.acinq.eclair.wire.protocol.{ChannelUpdate, Color} -import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, FeatureSupport, Features, MilliSatoshiLong, RealShortChannelId, ShortChannelId, TimestampSecondLong, randomKey} +import fr.acinq.eclair.router.Router.ChannelDesc +import fr.acinq.eclair.wire.protocol.Color +import fr.acinq.eclair.{BlockHeight, FeatureSupport, Features, MilliSatoshiLong, ShortChannelId, randomKey} import org.scalactic.Tolerance.convertNumericToPlusOrMinusWrapper import org.scalatest.funsuite.AnyFunSuite -import scodec.bits.HexStringSyntax -import scala.collection.immutable.SortedMap +import scala.concurrent.duration.DurationInt class GraphSpec extends AnyFunSuite { @@ -263,9 +262,9 @@ class GraphSpec extends AnyFunSuite { val edgeDE = makeEdge(6L, d, e, 9 msat, 0, capacity = 200000 sat) val graph = DirectedGraph(Seq(edgeAB, edgeBC, edgeCD, edgeDC, edgeCE, edgeDE)) - val path :: Nil = yenKshortestPaths(graph, a, e, 100000000 msat, + val path :: Nil = yenKshortestPaths(GraphWithBalanceEstimates(graph, 1 day), a, e, 100000000 msat, Set.empty, Set.empty, Set.empty, 1, - Right(HeuristicsConstants(1.0E-8, RelayFees(2000 msat, 500), RelayFees(50 msat, 20), useLogProbability = true)), + Right(HeuristicsConstants(1.0E-8, RelayFees(2000 msat, 500), RelayFees(50 msat, 20), useLogProbability = true, usePastRelaysData = false)), BlockHeight(714930), _ => true, includeLocalChannelCost = true) assert(path.path == Seq(edgeAB, edgeBC, edgeCE)) } @@ -287,7 +286,7 @@ class GraphSpec extends AnyFunSuite { val edgeDE = makeEdge(6L, d, e, 1 msat, 0, capacity = 200000 sat) val graph = DirectedGraph(Seq(edgeAB, edgeBC, edgeCD, edgeDC, edgeCE, edgeDE)) - val paths = yenKshortestPaths(graph, a, e, 90000000 msat, + val paths = yenKshortestPaths(GraphWithBalanceEstimates(graph, 1 day), a, e, 90000000 msat, Set.empty, Set.empty, Set.empty, 2, Left(WeightRatios(1, 0, 0, 0, RelayFees(0 msat, 0))), BlockHeight(714930), _ => true, includeLocalChannelCost = true) @@ -313,7 +312,7 @@ class GraphSpec extends AnyFunSuite { val edgeDE = makeEdge(6L, d, e, 1 msat, 0, capacity = 200000 sat) val graph = DirectedGraph(Seq(edgeAB, edgeBC, edgeCD, edgeDC, edgeCE, edgeDE)) - val paths = yenKshortestPaths(graph, a, e, 90000000 msat, + val paths = yenKshortestPaths(GraphWithBalanceEstimates(graph, 1 day), a, e, 90000000 msat, Set.empty, Set.empty, Set.empty, 2, Left(WeightRatios(1, 0, 0, 0, RelayFees(0 msat, 0))), BlockHeight(714930), _ => true, includeLocalChannelCost = true) @@ -346,7 +345,7 @@ class GraphSpec extends AnyFunSuite { val edgeGH = makeEdge(9L, g, h, 2 msat, 0, capacity = 100000 sat, minHtlc = 1000 msat) val graph = DirectedGraph(Seq(edgeCD, edgeDF, edgeCE, edgeED, edgeEF, edgeFG, edgeFH, edgeEG, edgeGH)) - val paths = yenKshortestPaths(graph, c, h, 10000000 msat, + val paths = yenKshortestPaths(GraphWithBalanceEstimates(graph, 1 day), c, h, 10000000 msat, Set.empty, Set.empty, Set.empty, 3, Left(WeightRatios(1, 0, 0, 0, RelayFees(0 msat, 0))), BlockHeight(714930), _ => true, includeLocalChannelCost = true) @@ -387,7 +386,7 @@ class GraphSpec extends AnyFunSuite { val edgeCB = makeEdge(3L, c, b, 2 msat, 4, capacity = 100000 sat, minHtlc = 1000 msat) val graph = DirectedGraph(Seq(edgeAB, edgeAC, edgeCB)) - val paths = yenKshortestPaths(graph, a, b, 10000000 msat, + val paths = yenKshortestPaths(GraphWithBalanceEstimates(graph, 1 day), a, b, 10000000 msat, Set.empty, Set.empty, Set.empty, 1, Left(WeightRatios(1, 0, 0, 0, RelayFees(0 msat, 0))), BlockHeight(714930), _ => true, includeLocalChannelCost = true) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala index 81cfc6c036..d023c81b04 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala @@ -37,6 +37,7 @@ import scodec.bits._ import scala.collection.immutable.SortedMap import scala.collection.mutable +import scala.concurrent.duration.DurationInt import scala.util.{Failure, Random, Success} /** @@ -50,12 +51,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { val (a, b, c, d, e, f) = (randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey) test("calculate simple route") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 1 msat, 10, cltvDelta = CltvExpiryDelta(1), balance_opt = Some(DEFAULT_AMOUNT_MSAT * 2)), makeEdge(2L, b, c, 1 msat, 10, cltvDelta = CltvExpiryDelta(1)), makeEdge(3L, c, d, 1 msat, 10, cltvDelta = CltvExpiryDelta(1)), makeEdge(4L, d, e, 1 msat, 10, cltvDelta = CltvExpiryDelta(1)) - )) + )), 1 day) val Success(route :: Nil) = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 1 :: 2 :: 3 :: 4 :: Nil) @@ -71,12 +72,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { val routeParams = DEFAULT_ROUTE_PARAMS.modify(_.boundaries.maxFeeFlat).setTo(1 msat) val maxFee = routeParams.getMaxFee(DEFAULT_AMOUNT_MSAT) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 10 msat, 10, cltvDelta = CltvExpiryDelta(1)), makeEdge(2L, b, c, 10 msat, 10, cltvDelta = CltvExpiryDelta(1)), makeEdge(3L, c, d, 10 msat, 10, cltvDelta = CltvExpiryDelta(1)), makeEdge(4L, d, e, 10 msat, 10, cltvDelta = CltvExpiryDelta(1)) - )) + )), 1 day) val Success(route :: Nil) = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, maxFee, numRoutes = 1, routeParams = routeParams, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 1 :: 2 :: 3 :: 4 :: Nil) @@ -112,17 +113,17 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { val amount = 10000 msat val expectedCost = 10007 msat - val graph = DirectedGraph(List( + val graph = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, feeBase = 1 msat, feeProportionalMillionth = 200, minHtlc = 0 msat), makeEdge(4L, a, e, feeBase = 1 msat, feeProportionalMillionth = 200, minHtlc = 0 msat), makeEdge(2L, b, c, feeBase = 1 msat, feeProportionalMillionth = 300, minHtlc = 0 msat), makeEdge(3L, c, d, feeBase = 1 msat, feeProportionalMillionth = 400, minHtlc = 0 msat), makeEdge(5L, e, f, feeBase = 1 msat, feeProportionalMillionth = 400, minHtlc = 0 msat), makeEdge(6L, f, d, feeBase = 1 msat, feeProportionalMillionth = 100, minHtlc = 0 msat) - )) + )), 1 day) val Success(route :: Nil) = findRoute(graph, a, d, amount, maxFee = 7 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) - val weightedPath = Graph.pathWeight(a, route2Edges(route), amount, BlockHeight(0), Left(NO_WEIGHT_RATIOS), includeLocalChannelCost = false) + val weightedPath = Graph.pathWeight(graph.balances, a, route2Edges(route), amount, BlockHeight(0), Left(NO_WEIGHT_RATIOS), includeLocalChannelCost = false) assert(route2Ids(route) == 4 :: 5 :: 6 :: Nil) assert(weightedPath.length == 3) assert(weightedPath.amount == expectedCost) @@ -138,25 +139,25 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("calculate route considering the direct channel pays no fees") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 5 msat, 0), // a -> b makeEdge(2L, a, d, 15 msat, 0), // a -> d this goes a bit closer to the target and asks for higher fees but is a direct channel makeEdge(3L, b, c, 5 msat, 0), // b -> c makeEdge(4L, c, d, 5 msat, 0), // c -> d makeEdge(5L, d, e, 5 msat, 0) // d -> e - )) + )), 1 day) val Success(route :: Nil) = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 2 :: 5 :: Nil) } test("calculate simple route (add and remove edges") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 0 msat, 0), makeEdge(2L, b, c, 0 msat, 0), makeEdge(3L, c, d, 0 msat, 0), makeEdge(4L, d, e, 0 msat, 0) - )) + )), 1 day) val Success(route1 :: Nil) = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route1) == 1 :: 2 :: 3 :: 4 :: Nil) @@ -174,12 +175,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { PublicKey(hex"029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c") // target ) - val graph = DirectedGraph(List( + val graph = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, f, g, 1 msat, 0), makeEdge(2L, g, h, 1 msat, 0), makeEdge(3L, h, i, 1 msat, 0), makeEdge(4L, f, h, 50 msat, 0) // more expensive but fee will be ignored since f is the payer - )) + )), 1 day) val Success(route :: Nil) = findRoute(graph, f, i, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 4 :: 3 :: Nil) @@ -193,12 +194,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { PublicKey(hex"029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c") // target ) - val graph = DirectedGraph(List( + val graph = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, f, g, 0 msat, 0), makeEdge(4L, f, i, 50 msat, 0), // our starting node F has a direct channel with I makeEdge(2L, g, h, 0 msat, 0), makeEdge(3L, h, i, 0 msat, 0) - )) + )), 1 day) val Success(route1 :: route2 :: Nil) = findRoute(graph, f, i, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 2, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route1) == 4 :: Nil) @@ -213,12 +214,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { PublicKey(hex"029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c") // I target ) - val graph = DirectedGraph(List( + val graph = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, f, g, 1 msat, 0, balance_opt = Some(DEFAULT_AMOUNT_MSAT + 50.msat)), // the maximum htlc allowed by this channel is only 50 msat greater than what we're sending makeEdge(2L, g, h, 1 msat, 0, maxHtlc = Some(DEFAULT_AMOUNT_MSAT + 50.msat)), makeEdge(3L, h, i, 1 msat, 0) - )) + )), 1 day) val Success(route :: Nil) = findRoute(graph, f, i, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 1 :: 2 :: 3 :: Nil) @@ -232,12 +233,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { PublicKey(hex"029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c") // I target ) - val graph = DirectedGraph(List( + val graph = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, f, g, 1 msat, 0, balance_opt = Some(DEFAULT_AMOUNT_MSAT + 50.msat)), // this channel requires a minimum amount that is larger than what we are sending makeEdge(2L, g, h, 1 msat, 0, minHtlc = DEFAULT_AMOUNT_MSAT + 50.msat), makeEdge(3L, h, i, 1 msat, 0) - )) + )), 1 day) val route = findRoute(graph, f, i, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route == Failure(RouteNotFound)) @@ -251,12 +252,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { PublicKey(hex"029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c") // I target ) - val graph = DirectedGraph(List( + val graph = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, f, g, 0 msat, 0), makeEdge(2L, g, h, 5 msat, 5), // expensive g -> h channel makeEdge(6L, g, h, 0 msat, 0), // cheap g -> h channel makeEdge(3L, h, i, 0 msat, 0) - )) + )), 1 day) val Success(route :: Nil) = findRoute(graph, f, i, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 1 :: 6 :: 3 :: Nil) @@ -270,46 +271,46 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { PublicKey(hex"029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c") // I target ) - val graph = DirectedGraph(List( + val graph = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, f, g, 0 msat, 0), makeEdge(2L, g, h, 5 msat, 5, balance_opt = Some(DEFAULT_AMOUNT_MSAT + 1.msat)), // expensive g -> h channel with enough balance makeEdge(6L, g, h, 0 msat, 0, balance_opt = Some(DEFAULT_AMOUNT_MSAT - 10.msat)), // cheap g -> h channel without enough balance makeEdge(3L, h, i, 0 msat, 0) - )) + )), 1 day) val Success(route :: Nil) = findRoute(graph, f, i, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 1 :: 2 :: 3 :: Nil) } test("calculate longer but cheaper route") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 0 msat, 0), makeEdge(2L, b, c, 0 msat, 0), makeEdge(3L, c, d, 0 msat, 0), makeEdge(4L, d, e, 0 msat, 0), makeEdge(5L, b, e, 10 msat, 10) - )) + )), 1 day) val Success(route :: Nil) = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 1 :: 2 :: 3 :: 4 :: Nil) } test("no local channels") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(2L, b, c, 0 msat, 0), makeEdge(4L, d, e, 0 msat, 0) - )) + )), 1 day) val route = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route == Failure(RouteNotFound)) } test("route not found") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 0 msat, 0), makeEdge(2L, b, c, 0 msat, 0), makeEdge(4L, d, e, 0 msat, 0) - )) + )), 1 day) val route = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route == Failure(RouteNotFound)) @@ -323,10 +324,10 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { val e = priv_e.publicKey val annE = makeNodeAnnouncement(priv_e, "E", Color(0, 0, 0), Nil, Features.empty) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(2L, b, c, 0 msat, 0), makeEdge(4L, c, d, 0 msat, 0) - )).addOrUpdateVertex(annA).addOrUpdateVertex(annE) + )).addOrUpdateVertex(annA).addOrUpdateVertex(annE), 1 day) assert(findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) == Failure(RouteNotFound)) assert(findRoute(g, b, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) == Failure(RouteNotFound)) @@ -348,60 +349,60 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(3L, c, d, 0 msat, 0) ) - val g = DirectedGraph(edgesHi) - val g1 = DirectedGraph(edgesLo) + val g = GraphWithBalanceEstimates(DirectedGraph(edgesHi), 1 day) + val g1 = GraphWithBalanceEstimates(DirectedGraph(edgesLo), 1 day) assert(findRoute(g, a, d, highAmount, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) == Failure(RouteNotFound)) assert(findRoute(g1, a, d, lowAmount, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) == Failure(RouteNotFound)) } test("route not found (balance too low)") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 1 msat, 2, minHtlc = 10000 msat), makeEdge(2L, b, c, 1 msat, 2, minHtlc = 10000 msat), makeEdge(3L, c, d, 1 msat, 2, minHtlc = 10000 msat) - )) + )), 1 day) assert(findRoute(g, a, d, 15000 msat, 100 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)).isSuccess) // not enough balance on the last edge - val g1 = DirectedGraph(List( + val g1 = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 1 msat, 2, minHtlc = 10000 msat), makeEdge(2L, b, c, 1 msat, 2, minHtlc = 10000 msat), makeEdge(3L, c, d, 1 msat, 2, minHtlc = 10000 msat, balance_opt = Some(10000 msat)) - )) + )), 1 day) // not enough balance on intermediate edge (taking fee into account) - val g2 = DirectedGraph(List( + val g2 = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 1 msat, 2, minHtlc = 10000 msat), makeEdge(2L, b, c, 1 msat, 2, minHtlc = 10000 msat, balance_opt = Some(15000 msat)), makeEdge(3L, c, d, 1 msat, 2, minHtlc = 10000 msat) - )) + )), 1 day) // no enough balance on first edge (taking fee into account) - val g3 = DirectedGraph(List( + val g3 = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 1 msat, 2, minHtlc = 10000 msat, balance_opt = Some(15000 msat)), makeEdge(2L, b, c, 1 msat, 2, minHtlc = 10000 msat), makeEdge(3L, c, d, 1 msat, 2, minHtlc = 10000 msat) - )) + )), 1 day) Seq(g1, g2, g3).foreach(g => assert(findRoute(g, a, d, 15000 msat, 100 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) == Failure(RouteNotFound))) } test("route to self") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 0 msat, 0), makeEdge(2L, b, c, 0 msat, 0), makeEdge(3L, c, d, 0 msat, 0) - )) + )), 1 day) val route = findRoute(g, a, a, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route == Failure(CannotRouteToSelf)) } test("route to immediate neighbor") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 0 msat, 0, balance_opt = Some(DEFAULT_AMOUNT_MSAT)), makeEdge(2L, b, c, 0 msat, 0), makeEdge(3L, c, d, 0 msat, 0), makeEdge(4L, d, e, 0 msat, 0) - )) + )), 1 day) val Success(route :: Nil) = findRoute(g, a, b, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 1 :: Nil) @@ -409,12 +410,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("directed graph") { // a->e works, e->a fails - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 0 msat, 0), makeEdge(2L, b, c, 0 msat, 0), makeEdge(3L, c, d, 0 msat, 0), makeEdge(4L, d, e, 0 msat, 0) - )) + )), 1 day) val Success(route1 :: Nil) = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route1) == 1 :: 2 :: 3 :: 4 :: Nil) @@ -446,26 +447,26 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { GraphEdge(ChannelDesc(ShortChannelId(4L), e, d), HopRelayParams.FromAnnouncement(ued), DEFAULT_CAPACITY, None) ) - val g = DirectedGraph(edges) + val g = GraphWithBalanceEstimates(DirectedGraph(edges), 1 day) val Success(route :: Nil) = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route.hops == channelHopFromUpdate(a, b, uab) :: channelHopFromUpdate(b, c, ubc) :: channelHopFromUpdate(c, d, ucd) :: channelHopFromUpdate(d, e, ude) :: Nil) } test("blacklist routes") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 0 msat, 0), makeEdge(2L, b, c, 0 msat, 0), makeEdge(3L, c, d, 0 msat, 0), makeEdge(4L, d, e, 0 msat, 0) - )) + )), 1 day) val route1 = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, ignoredEdges = Set(ChannelDesc(ShortChannelId(3L), c, d)), routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route1 == Failure(RouteNotFound)) // verify that we left the graph untouched - assert(g.containsEdge(ChannelDesc(ShortChannelId(3), c, d))) - assert(g.containsVertex(c)) - assert(g.containsVertex(d)) + assert(g.graph.containsEdge(ChannelDesc(ShortChannelId(3), c, d))) + assert(g.graph.containsVertex(c)) + assert(g.graph.containsVertex(d)) // make sure we can find a route if without the blacklist val Success(route2 :: Nil) = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) @@ -473,11 +474,11 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("route to a destination that is not in the graph (with assisted routes)") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 10 msat, 10), makeEdge(2L, b, c, 10 msat, 10), makeEdge(3L, c, d, 10 msat, 10) - )) + )), 1 day) val route = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route == Failure(RouteNotFound)) @@ -489,10 +490,10 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("route from a source that is not in the graph (with assisted routes)") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(2L, b, c, 10 msat, 10), makeEdge(3L, c, d, 10 msat, 10) - )) + )), 1 day) val route = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route == Failure(RouteNotFound)) @@ -504,12 +505,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("verify that extra hops takes precedence over known channels") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 10 msat, 10), makeEdge(2L, b, c, 10 msat, 10), makeEdge(3L, c, d, 10 msat, 10), makeEdge(4L, d, e, 10 msat, 10) - )) + )), 1 day) val Success(route1 :: Nil) = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route1) == 1 :: 2 :: 3 :: 4 :: Nil) @@ -572,7 +573,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { .zipWithIndex // ((0, 1), 0) :: ((1, 2), 1) :: ... .map { case ((na, nb), index) => makeEdge(index, na, nb, 5 msat, 0) } - val g = DirectedGraph(edges) + val g = GraphWithBalanceEstimates(DirectedGraph(edges), 1 day) assert(findRoute(g, nodes(0), nodes(18), DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)).map(r => route2Ids(r.head)) == Success(0 until 18)) assert(findRoute(g, nodes(0), nodes(19), DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)).map(r => route2Ids(r.head)) == Success(0 until 19)) @@ -590,7 +591,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { val expensiveShortEdge = makeEdge(99, nodes(2), nodes(48), 1000 msat, 0) // expensive shorter route - val g = DirectedGraph(expensiveShortEdge :: edges) + val g = GraphWithBalanceEstimates(DirectedGraph(expensiveShortEdge :: edges), 1 day) val Success(route :: Nil) = findRoute(g, nodes(0), nodes(49), DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 0 :: 1 :: 99 :: 48 :: Nil) @@ -598,14 +599,14 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("ignore cheaper route when it has more than the requested CLTV") { val f = randomKey().publicKey - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1, a, b, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, CltvExpiryDelta(50)), makeEdge(2, b, c, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, CltvExpiryDelta(50)), makeEdge(3, c, d, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, CltvExpiryDelta(50)), makeEdge(4, a, e, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, CltvExpiryDelta(9)), makeEdge(5, e, f, feeBase = 5 msat, 0, minHtlc = 0 msat, maxHtlc = None, CltvExpiryDelta(9)), makeEdge(6, f, d, feeBase = 5 msat, 0, minHtlc = 0 msat, maxHtlc = None, CltvExpiryDelta(9)) - )) + )), 1 day) val Success(route :: Nil) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.modify(_.boundaries.maxCltv).setTo(CltvExpiryDelta(28)), currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 4 :: 5 :: 6 :: Nil) @@ -613,27 +614,27 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("ignore cheaper route when it grows longer than the requested size") { val f = randomKey().publicKey - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1, a, b, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, CltvExpiryDelta(9)), makeEdge(2, b, c, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, CltvExpiryDelta(9)), makeEdge(3, c, d, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, CltvExpiryDelta(9)), makeEdge(4, d, e, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, CltvExpiryDelta(9)), makeEdge(5, e, f, feeBase = 5 msat, 0, minHtlc = 0 msat, maxHtlc = None, CltvExpiryDelta(9)), makeEdge(6, b, f, feeBase = 5 msat, 0, minHtlc = 0 msat, maxHtlc = None, CltvExpiryDelta(9)) - )) + )), 1 day) val Success(route :: Nil) = findRoute(g, a, f, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.modify(_.boundaries.maxRouteLength).setTo(3), currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 1 :: 6 :: Nil) } test("ignore loops") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 10 msat, 10), makeEdge(2L, b, c, 10 msat, 10), makeEdge(3L, c, a, 10 msat, 10), makeEdge(4L, c, d, 10 msat, 10), makeEdge(5L, d, e, 10 msat, 10) - )) + )), 1 day) val Success(route :: Nil) = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 1 :: 2 :: 4 :: 5 :: Nil) @@ -641,7 +642,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("ensure the route calculation terminates correctly when selecting 0-fees edges") { // the graph contains a possible 0-cost path that goes back on its steps ( e -> f, f -> e ) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 10 msat, 10), // a -> b makeEdge(2L, b, c, 10 msat, 10), makeEdge(4L, c, d, 10 msat, 10), @@ -649,7 +650,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(6L, e, f, 0 msat, 0), // e -> f makeEdge(6L, f, e, 0 msat, 0), // e <- f makeEdge(5L, e, d, 0 msat, 0) // e -> d - )) + )), 1 day) val Success(route :: Nil) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Ids(route) == 1 :: 3 :: 5 :: Nil) @@ -674,7 +675,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { PublicKey(hex"03fc5b91ce2d857f146fd9b986363374ffe04dc143d8bcd6d7664c8873c463cdfc") ) - val g1 = DirectedGraph(Seq( + val g1 = GraphWithBalanceEstimates(DirectedGraph(Seq( makeEdge(1L, d, a, 1 msat, 0, balance_opt = Some(DEFAULT_AMOUNT_MSAT + 4.msat)), makeEdge(2L, d, e, 1 msat, 0, balance_opt = Some(DEFAULT_AMOUNT_MSAT + 3.msat)), makeEdge(3L, a, e, 1 msat, 0, balance_opt = Some(DEFAULT_AMOUNT_MSAT + 3.msat)), @@ -682,7 +683,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(5L, e, f, 1 msat, 0, balance_opt = Some(DEFAULT_AMOUNT_MSAT)), makeEdge(6L, b, c, 1 msat, 0, balance_opt = Some(DEFAULT_AMOUNT_MSAT + 1.msat)), makeEdge(7L, c, f, 1 msat, 0, balance_opt = Some(DEFAULT_AMOUNT_MSAT)) - )) + )), 1 day) val fourShortestPaths = Graph.yenKshortestPaths(g1, d, f, DEFAULT_AMOUNT_MSAT, Set.empty, Set.empty, Set.empty, pathsToFind = 4, Left(NO_WEIGHT_RATIOS), BlockHeight(0), noopBoundaries, includeLocalChannelCost = false) assert(fourShortestPaths.size == 4) @@ -710,7 +711,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { PublicKey(hex"03fc5b91ce2d857f146fd9b986363374ffe04dc143d8bcd6d7664c8873c463cdfc") ) - val graph = DirectedGraph(Seq( + val graph = GraphWithBalanceEstimates(DirectedGraph(Seq( makeEdge(10L, c, e, 2 msat, 0), makeEdge(20L, c, d, 3 msat, 0), makeEdge(30L, d, f, 4 msat, 5), // D- > F has a higher cost to distinguish it from the 2nd cheapest route @@ -720,7 +721,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(70L, f, g, 2 msat, 0), makeEdge(80L, f, h, 1 msat, 0), makeEdge(90L, g, h, 2 msat, 0) - )) + )), 1 day) val twoShortestPaths = Graph.yenKshortestPaths(graph, c, h, DEFAULT_AMOUNT_MSAT, Set.empty, Set.empty, Set.empty, pathsToFind = 2, Left(NO_WEIGHT_RATIOS), BlockHeight(0), noopBoundaries, includeLocalChannelCost = false) @@ -736,7 +737,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { val f = randomKey().publicKey // simple graph with only 2 possible paths from A to F - val graph = DirectedGraph(Seq( + val graph = GraphWithBalanceEstimates(DirectedGraph(Seq( makeEdge(1L, a, b, 1 msat, 0), makeEdge(1L, b, a, 1 msat, 0), makeEdge(2L, b, c, 1 msat, 0), @@ -750,7 +751,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(5L, e, d, 1 msat, 0), makeEdge(6L, e, f, 1 msat, 0), makeEdge(6L, f, e, 1 msat, 0) - )) + )), 1 day) // we ask for 3 shortest paths but only 2 can be found val foundPaths = Graph.yenKshortestPaths(graph, a, f, DEFAULT_AMOUNT_MSAT, Set.empty, Set.empty, Set.empty, pathsToFind = 3, Left(NO_WEIGHT_RATIOS), BlockHeight(0), noopBoundaries, includeLocalChannelCost = false) @@ -770,7 +771,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // A -> B -> C -> D has total cost of 10000005 // A -> E -> C -> D has total cost of 10000103 !! // A -> E -> F -> D has total cost of 10000006 - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, feeBase = 1 msat, 0), makeEdge(2L, b, c, feeBase = 2 msat, 0), makeEdge(3L, c, d, feeBase = 3 msat, 0), @@ -778,12 +779,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(5L, e, f, feeBase = 3 msat, 0), makeEdge(6L, f, d, feeBase = 3 msat, 0), makeEdge(7L, e, c, feeBase = 100 msat, 0) - )) + )), 1 day) for (_ <- 0 to 10) { val Success(routes) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, strictFee, numRoutes = 3, routeParams = strictFeeParams, currentBlockHeight = BlockHeight(400000)) assert(routes.length == 2, routes) - val weightedPath = Graph.pathWeight(a, route2Edges(routes.head), DEFAULT_AMOUNT_MSAT, BlockHeight(400000), Left(NO_WEIGHT_RATIOS), includeLocalChannelCost = false) + val weightedPath = Graph.pathWeight(g.balances, a, route2Edges(routes.head), DEFAULT_AMOUNT_MSAT, BlockHeight(400000), Left(NO_WEIGHT_RATIOS), includeLocalChannelCost = false) val totalFees = weightedPath.amount - DEFAULT_AMOUNT_MSAT // over the three routes we could only get the 2 cheapest because the third is too expensive (over 7 msat of fees) assert(totalFees == 5.msat || totalFees == 6.msat) @@ -798,7 +799,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // A -> B -> C -> D is 'fee optimized', lower fees route (totFees = 2, totCltv = 4000) // A -> E -> F -> D is 'timeout optimized', lower CLTV route (totFees = 3, totCltv = 18) // A -> E -> C -> D is 'capacity optimized', more recent channel/larger capacity route - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, feeBase = 0 msat, 1000, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(13)), makeEdge(4L, a, e, feeBase = 0 msat, 1000, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(12)), makeEdge(2L, b, c, feeBase = 1 msat, 1000, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(500)), @@ -806,7 +807,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(5L, e, f, feeBase = 2 msat, 1000, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(9)), makeEdge(6L, f, d, feeBase = 2 msat, 1000, minHtlc = 0 msat, capacity = defaultCapacity, cltvDelta = CltvExpiryDelta(9)), makeEdge(7L, e, c, feeBase = 2 msat, 1000, minHtlc = 0 msat, capacity = largeCapacity, cltvDelta = CltvExpiryDelta(12)) - )) + )), 1 day) val Success(routeFeeOptimized :: Nil) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(route2Nodes(routeFeeOptimized) == (a, b) :: (b, c) :: (c, d) :: Nil) @@ -833,14 +834,14 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("prefer going through an older channel if fees and CLTV are the same") { val currentBlockHeight = BlockHeight(554000) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(ShortChannelId.fromCoordinates(s"${currentBlockHeight.toLong}x0x1").success.value.toLong, a, b, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), makeEdge(ShortChannelId.fromCoordinates(s"${currentBlockHeight.toLong}x0x4").success.value.toLong, a, e, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), makeEdge(ShortChannelId.fromCoordinates(s"${currentBlockHeight.toLong - 3000}x0x2").success.value.toLong, b, c, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), // younger channel makeEdge(ShortChannelId.fromCoordinates(s"${currentBlockHeight.toLong - 3000}x0x3").success.value.toLong, c, d, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), makeEdge(ShortChannelId.fromCoordinates(s"${currentBlockHeight.toLong}x0x5").success.value.toLong, e, f, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), makeEdge(ShortChannelId.fromCoordinates(s"${currentBlockHeight.toLong}x0x6").success.value.toLong, f, d, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(144)) - )) + )), 1 day) val Success(routeScoreOptimized :: Nil) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT / 2, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = Left(WeightRatios( baseFactor = 0.01, @@ -854,14 +855,14 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("prefer a route with a smaller total CLTV if fees and score are the same") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1, a, b, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(12)), makeEdge(4, a, e, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(12)), makeEdge(2, b, c, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(10)), // smaller CLTV makeEdge(3, c, d, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(12)), makeEdge(5, e, f, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(12)), makeEdge(6, f, d, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(12)) - )) + )), 1 day) val Success(routeScoreOptimized :: Nil) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = Left(WeightRatios( baseFactor = 0.01, @@ -877,14 +878,14 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("avoid a route that breaks off the max CLTV") { // A -> B -> C -> D is cheaper but has a total CLTV > 2016! // A -> E -> F -> D is more expensive but has a total CLTV < 2016 - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1, a, b, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), makeEdge(4, a, e, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), makeEdge(2, b, c, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(1000)), makeEdge(3, c, d, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(900)), makeEdge(5, e, f, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), makeEdge(6, f, d, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(144)) - )) + )), 1 day) val Success(routeScoreOptimized :: Nil) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT / 2, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = Left(WeightRatios( baseFactor = 0.01, @@ -927,7 +928,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { ) ) - val g = DirectedGraph.makeGraph(updates, Seq.empty) + val g = GraphWithBalanceEstimates(DirectedGraph.makeGraph(updates, Seq.empty), 1 day) val params = DEFAULT_ROUTE_PARAMS .modify(_.boundaries.maxCltv).setTo(CltvExpiryDelta(1008)) .modify(_.heuristics).setTo(Left(WeightRatios(baseFactor = 0, cltvDeltaFactor = 0.15, ageFactor = 0.35, capacityFactor = 0.5, hopCost = RelayFees(0 msat, 0)))) @@ -962,12 +963,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("calculate multipart route to neighbor (many channels, known balance)") { val amount = 60000 msat - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 50 msat, 100, minHtlc = 1 msat, balance_opt = Some(15000 msat)), makeEdge(2L, a, b, 15 msat, 10, minHtlc = 1 msat, balance_opt = Some(21000 msat)), makeEdge(3L, a, b, 1 msat, 50, minHtlc = 1 msat, balance_opt = Some(17000 msat)), makeEdge(4L, a, b, 100 msat, 20, minHtlc = 1 msat, balance_opt = Some(16000 msat)), - )) + )), 1 day) // We set max-parts to 3, but it should be ignored when sending to a direct neighbor. val routeParams = DEFAULT_ROUTE_PARAMS.copy(mpp = MultiPartParams(2500 msat, 3)) @@ -991,12 +992,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("calculate multipart route to neighbor (single channel, known balance)") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 50 msat, 100, minHtlc = 1 msat, balance_opt = Some(25000 msat)), makeEdge(2L, a, c, 1 msat, 0, minHtlc = 1 msat, balance_opt = Some(50000 msat)), makeEdge(3L, c, b, 1 msat, 0, minHtlc = 1 msat), makeEdge(4L, a, d, 1 msat, 0, minHtlc = 1 msat, balance_opt = Some(45000 msat)), - )) + )), 1 day) val amount = 25000 msat val Success(routes) = findMultiPartRoute(g, a, b, amount, 1 msat, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) @@ -1006,13 +1007,13 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("calculate multipart route to neighbor (many channels, some balance unknown)") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 50 msat, 100, minHtlc = 1 msat, balance_opt = Some(15000 msat)), makeEdge(2L, a, b, 15 msat, 10, minHtlc = 1 msat, balance_opt = Some(25000 msat)), makeEdge(3L, a, b, 1 msat, 50, minHtlc = 1 msat, balance_opt = None, capacity = 20 sat), makeEdge(4L, a, b, 100 msat, 20, minHtlc = 1 msat, balance_opt = Some(10000 msat)), makeEdge(5L, a, d, 1 msat, 0, minHtlc = 1 msat, balance_opt = Some(45000 msat)), - )) + )), 1 day) val amount = 65000 msat val Success(routes) = findMultiPartRoute(g, a, b, amount, 1 msat, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) @@ -1023,7 +1024,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("calculate multipart route to neighbor (many channels, some empty)") { val amount = 35000 msat - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 50 msat, 100, minHtlc = 1 msat, balance_opt = Some(15000 msat)), makeEdge(2L, a, b, 15 msat, 10, minHtlc = 1 msat, balance_opt = Some(0 msat)), makeEdge(3L, a, b, 1 msat, 50, minHtlc = 1 msat, balance_opt = None, capacity = 15 sat), @@ -1031,7 +1032,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(5L, a, b, 100 msat, 20, minHtlc = 1 msat, balance_opt = Some(10000 msat)), makeEdge(6L, a, d, 1 msat, 0, minHtlc = 1 msat, balance_opt = Some(45000 msat)), makeEdge(7L, a, d, 0 msat, 0, minHtlc = 0 msat, balance_opt = Some(0 msat)), - )) + )), 1 day) { val Success(routes) = findMultiPartRoute(g, a, b, amount, 1 msat, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) @@ -1050,14 +1051,14 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("calculate multipart route to neighbor (ignored channels)") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 50 msat, 100, minHtlc = 1 msat, balance_opt = Some(15000 msat)), makeEdge(2L, a, b, 15 msat, 10, minHtlc = 1 msat, balance_opt = Some(25000 msat)), makeEdge(3L, a, b, 1 msat, 50, minHtlc = 1 msat, balance_opt = None, capacity = 50 sat), makeEdge(4L, a, b, 100 msat, 20, minHtlc = 1 msat, balance_opt = Some(10000 msat)), makeEdge(5L, a, b, 1 msat, 10, minHtlc = 1 msat, balance_opt = None, capacity = 10 sat), makeEdge(6L, a, d, 1 msat, 0, minHtlc = 1 msat, balance_opt = Some(45000 msat)), - )) + )), 1 day) val amount = 20000 msat val ignoredEdges = Set(ChannelDesc(ShortChannelId(2L), a, b), ChannelDesc(ShortChannelId(3L), a, b)) @@ -1071,12 +1072,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { val edge_ab_1 = makeEdge(1L, a, b, 50 msat, 100, minHtlc = 1 msat, balance_opt = Some(15000 msat)) val edge_ab_2 = makeEdge(2L, a, b, 15 msat, 10, minHtlc = 1 msat, balance_opt = Some(25000 msat)) val edge_ab_3 = makeEdge(3L, a, b, 1 msat, 50, minHtlc = 1 msat, balance_opt = None, capacity = 15 sat) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( edge_ab_1, edge_ab_2, edge_ab_3, makeEdge(4L, a, d, 1 msat, 0, minHtlc = 1 msat, balance_opt = Some(45000 msat)), - )) + )), 1 day) val amount = 50000 msat // These pending HTLCs will have already been taken into account in the edge's `balance_opt` field: findMultiPartRoute @@ -1088,12 +1089,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("calculate multipart route to neighbor (restricted htlc_maximum_msat)") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 25 msat, 15, minHtlc = 1 msat, maxHtlc = Some(5000 msat), balance_opt = Some(18000 msat)), makeEdge(2L, a, b, 15 msat, 10, minHtlc = 1 msat, maxHtlc = Some(5000 msat), balance_opt = Some(23000 msat)), makeEdge(3L, a, b, 1 msat, 50, minHtlc = 1 msat, maxHtlc = Some(5000 msat), balance_opt = Some(21000 msat)), makeEdge(4L, a, d, 1 msat, 0, minHtlc = 1 msat, balance_opt = Some(45000 msat)), - )) + )), 1 day) val amount = 50000 msat val Success(routes) = findMultiPartRoute(g, a, b, amount, 1 msat, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) @@ -1104,12 +1105,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("calculate multipart route to neighbor (restricted htlc_minimum_msat)") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 25 msat, 15, minHtlc = 2500 msat, balance_opt = Some(18000 msat)), makeEdge(2L, a, b, 15 msat, 10, minHtlc = 2500 msat, balance_opt = Some(7000 msat)), makeEdge(3L, a, b, 1 msat, 50, minHtlc = 2500 msat, balance_opt = Some(10000 msat)), makeEdge(4L, a, d, 1 msat, 0, minHtlc = 1 msat, balance_opt = Some(45000 msat)), - )) + )), 1 day) val amount = 30000 msat val routeParams = DEFAULT_ROUTE_PARAMS.copy(mpp = MultiPartParams(2500 msat, 5)) @@ -1120,13 +1121,13 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("calculate multipart route to neighbor (through remote channels)") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 25 msat, 15, minHtlc = 1000 msat, balance_opt = Some(18000 msat)), makeEdge(2L, a, b, 15 msat, 10, minHtlc = 1000 msat, balance_opt = Some(7000 msat)), makeEdge(3L, a, c, 1000 msat, 10000, minHtlc = 1000 msat, balance_opt = Some(10000 msat)), makeEdge(4L, c, b, 10 msat, 1000, minHtlc = 1000 msat), makeEdge(5L, a, d, 1 msat, 0, minHtlc = 1 msat, balance_opt = Some(25000 msat)), - )) + )), 1 day) val amount = 30000 msat val maxFeeTooLow = findMultiPartRoute(g, a, b, amount, 1 msat, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) @@ -1139,12 +1140,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("cannot find multipart route to neighbor (not enough balance)") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 0 msat, 0, minHtlc = 1 msat, balance_opt = Some(15000 msat)), makeEdge(2L, a, b, 0 msat, 0, minHtlc = 1 msat, balance_opt = Some(5000 msat)), makeEdge(3L, a, b, 0 msat, 0, minHtlc = 1 msat, balance_opt = Some(10000 msat)), makeEdge(4L, a, d, 0 msat, 0, minHtlc = 1 msat, balance_opt = Some(45000 msat)), - )) + )), 1 day) { val result = findMultiPartRoute(g, a, b, 40000 msat, 1 msat, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) @@ -1157,23 +1158,23 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("cannot find multipart route to neighbor (not enough capacity)") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 0 msat, 0, minHtlc = 1 msat, capacity = 1500 sat), makeEdge(2L, a, b, 0 msat, 0, minHtlc = 1 msat, capacity = 2000 sat), makeEdge(3L, a, b, 0 msat, 0, minHtlc = 1 msat, capacity = 1200 sat), makeEdge(4L, a, d, 0 msat, 0, minHtlc = 1 msat, capacity = 4500 sat), - )) + )), 1 day) val result = findMultiPartRoute(g, a, b, 5000000 msat, 1 msat, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(result == Failure(RouteNotFound)) } test("cannot find multipart route to neighbor (restricted htlc_minimum_msat)") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 25 msat, 15, minHtlc = 5000 msat, balance_opt = Some(6000 msat)), makeEdge(2L, a, b, 15 msat, 10, minHtlc = 5000 msat, balance_opt = Some(7000 msat)), makeEdge(3L, a, d, 0 msat, 0, minHtlc = 5000 msat, balance_opt = Some(9000 msat)), - )) + )), 1 day) { val result = findMultiPartRoute(g, a, b, 10000 msat, 1 msat, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) @@ -1193,14 +1194,14 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // +--- B --- D ---+ val (amount, maxFee) = (30000 msat, 150 msat) val edge_ab = makeEdge(1L, a, b, 50 msat, 100, minHtlc = 1 msat, balance_opt = Some(15000 msat)) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( edge_ab, makeEdge(2L, b, d, 15 msat, 0, minHtlc = 1 msat, capacity = 25 sat), makeEdge(3L, d, e, 15 msat, 0, minHtlc = 0 msat, capacity = 20 sat), makeEdge(4L, a, c, 1 msat, 50, minHtlc = 1 msat, balance_opt = Some(10000 msat)), makeEdge(5L, a, c, 1 msat, 50, minHtlc = 1 msat, balance_opt = Some(8000 msat)), makeEdge(6L, c, e, 50 msat, 30, minHtlc = 1 msat, capacity = 20 sat), - )) + )), 1 day) { val Success(routes) = findMultiPartRoute(g, a, e, amount, maxFee, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) @@ -1245,13 +1246,13 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // +--- B --- D ---+ // Our balance and the amount we want to send are below the minimum part amount. val routeParams = DEFAULT_ROUTE_PARAMS.copy(mpp = MultiPartParams(5000 msat, 5)) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 50 msat, 100, minHtlc = 1 msat, balance_opt = Some(1500 msat)), makeEdge(2L, b, d, 15 msat, 0, minHtlc = 1 msat, capacity = 25 sat), makeEdge(3L, d, e, 15 msat, 0, minHtlc = 1 msat, capacity = 20 sat), makeEdge(4L, a, c, 1 msat, 50, minHtlc = 1 msat, balance_opt = Some(1000 msat)), makeEdge(5L, c, e, 50 msat, 30, minHtlc = 1 msat, capacity = 20 sat), - )) + )), 1 day) { // We can send single-part tiny payments. @@ -1269,11 +1270,11 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("calculate multipart route to remote node (single path)") { val (amount, maxFee) = (100000 msat, 500 msat) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 50 msat, 100, minHtlc = 1 msat, balance_opt = Some(500000 msat)), makeEdge(2L, b, c, 10 msat, 30, minHtlc = 1 msat, capacity = 150 sat), makeEdge(3L, c, d, 15 msat, 50, minHtlc = 1 msat, capacity = 150 sat), - )) + )), 1 day) val Success(routes) = findMultiPartRoute(g, a, d, amount, maxFee, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) checkRouteAmounts(routes, amount, maxFee) @@ -1289,7 +1290,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // +----- E -------+ val (amount, maxFee) = (400000 msat, 250 msat) val edge_ab = makeEdge(1L, a, b, 50 msat, 100, minHtlc = 1 msat, balance_opt = Some(500000 msat)) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( edge_ab, makeEdge(2L, b, c, 10 msat, 30, minHtlc = 1 msat, capacity = 150 sat), makeEdge(3L, c, d, 15 msat, 50, minHtlc = 1 msat, capacity = 150 sat), @@ -1297,7 +1298,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(5L, d, f, 5 msat, 50, minHtlc = 1 msat, capacity = 300 sat), makeEdge(6L, b, e, 15 msat, 80, minHtlc = 1 msat, capacity = 210 sat), makeEdge(7L, e, f, 15 msat, 100, minHtlc = 1 msat, capacity = 200 sat), - )) + )), 1 day) { val Success(routes) = findMultiPartRoute(g, a, f, amount, maxFee, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) @@ -1359,7 +1360,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(100, a, c, 5 msat, 1000, minHtlc = 1 msat, capacity = 25000 sat, balance_opt = Some(20_000_000 msat)), makeEdge(101, c, d, 5 msat, 1000, minHtlc = 1 msat, capacity = 25000 sat), ) - val g = DirectedGraph(preferredEdges ++ cheapEdges) + val g = GraphWithBalanceEstimates(DirectedGraph(preferredEdges ++ cheapEdges), 1 day) { val amount = 15_000_000 msat @@ -1401,7 +1402,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // | +-------------------+ | // +---------- E ----------+ val (amount, maxFee) = (25000 msat, 5 msat) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 1 msat, 0, minHtlc = 1 msat, balance_opt = Some(75000 msat)), makeEdge(2L, b, c, 1 msat, 0, minHtlc = 1 msat, capacity = 150 sat), makeEdge(3L, c, f, 1 msat, 0, minHtlc = 1 msat, capacity = 150 sat), @@ -1412,7 +1413,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(8L, a, f, 1 msat, 0, minHtlc = 1 msat, balance_opt = Some(10000 msat)), makeEdge(9L, a, e, 1 msat, 0, minHtlc = 1 msat, balance_opt = Some(18000 msat)), makeEdge(10L, e, f, 1 msat, 0, minHtlc = 1 msat, capacity = 15 sat), - )) + )), 1 day) val ignoredNodes = Set(d) val ignoredChannels = Set(ChannelDesc(ShortChannelId(2L), b, c)) @@ -1428,7 +1429,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // | | // +----- D -----+ val (amount, maxFee) = (15000 msat, 5 msat) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( // The A -> B -> E path is impossible because the A -> B balance is lower than the B -> E htlc_minimum_msat. makeEdge(1L, a, b, 1 msat, 0, minHtlc = 500 msat, balance_opt = Some(7000 msat)), makeEdge(2L, b, e, 1 msat, 0, minHtlc = 10000 msat, capacity = 50 sat), @@ -1436,7 +1437,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(4L, c, e, 1 msat, 0, minHtlc = 500 msat, maxHtlc = Some(4000 msat), capacity = 50 sat), makeEdge(5L, a, d, 1 msat, 0, minHtlc = 500 msat, balance_opt = Some(10000 msat)), makeEdge(6L, d, e, 1 msat, 0, minHtlc = 500 msat, maxHtlc = Some(4000 msat), capacity = 50 sat), - )) + )), 1 day) val Success(routes) = findMultiPartRoute(g, a, e, amount, maxFee, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) checkRouteAmounts(routes, amount, maxFee) @@ -1460,7 +1461,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // +---+ | | +---+ | // | D |-----+ +--->| F |<-----+ // +---+ +---+ - val g = DirectedGraph(Seq( + val g = GraphWithBalanceEstimates(DirectedGraph(Seq( makeEdge(1L, d, a, 100 msat, 1000, minHtlc = 1000 msat, balance_opt = Some(80000 msat)), makeEdge(2L, d, e, 100 msat, 1000, minHtlc = 1500 msat, balance_opt = Some(20000 msat)), makeEdge(3L, a, e, 5 msat, 50, minHtlc = 1200 msat, capacity = 100 sat), @@ -1468,7 +1469,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(5L, e, b, 10 msat, 100, minHtlc = 1100 msat, capacity = 75 sat), makeEdge(6L, b, c, 5 msat, 50, minHtlc = 1000 msat, capacity = 20 sat), makeEdge(7L, c, f, 5 msat, 10, minHtlc = 1500 msat, capacity = 50 sat) - )) + )), 1 day) val routeParams = DEFAULT_ROUTE_PARAMS.copy(mpp = MultiPartParams(1500 msat, 10)) { @@ -1508,12 +1509,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // A D (---) E (---) F // +--- C ---+ val (amount, maxFeeE, maxFeeF) = (10000 msat, 50 msat, 100 msat) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 1 msat, 0, minHtlc = 1 msat, maxHtlc = Some(4000 msat), balance_opt = Some(7000 msat)), makeEdge(2L, b, d, 1 msat, 0, minHtlc = 1 msat, capacity = 50 sat), makeEdge(3L, a, c, 1 msat, 0, minHtlc = 1 msat, maxHtlc = Some(4000 msat), balance_opt = Some(6000 msat)), makeEdge(4L, c, d, 1 msat, 0, minHtlc = 1 msat, capacity = 40 sat), - )) + )), 1 day) val extraEdges = Set( makeEdge(10L, d, e, 10 msat, 100, minHtlc = 500 msat, capacity = 15 sat), makeEdge(11L, e, f, 5 msat, 100, minHtlc = 500 msat, capacity = 10 sat), @@ -1543,7 +1544,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { val (amount, maxFee) = (15000 msat, 100 msat) val edge_ab = makeEdge(1L, a, b, 1 msat, 0, minHtlc = 100 msat, balance_opt = Some(5000 msat)) val edge_be = makeEdge(2L, b, e, 1 msat, 0, minHtlc = 100 msat, capacity = 5 sat) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( // The A -> B -> E route is the most economic one, but we already have a pending HTLC in it. edge_ab, edge_be, @@ -1551,7 +1552,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(4L, c, e, 50 msat, 0, minHtlc = 100 msat, capacity = 25 sat), makeEdge(5L, a, d, 50 msat, 0, minHtlc = 100 msat, balance_opt = Some(10000 msat)), makeEdge(6L, d, e, 50 msat, 0, minHtlc = 100 msat, capacity = 25 sat), - )) + )), 1 day) val pendingHtlcs = Seq(Route(5000 msat, graphEdgeToHop(edge_ab) :: graphEdgeToHop(edge_be) :: Nil, None)) val Success(routes) = findMultiPartRoute(g, a, e, amount, maxFee, pendingHtlcs = pendingHtlcs, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) @@ -1580,7 +1581,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { for (_ <- 1 to 100) { val amount = (100 + Random.nextLong(200000)).msat val maxFee = 50.msat.max(amount * 0.03) - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, d, f, Random.nextLong(250).msat, Random.nextInt(10000), minHtlc = Random.nextLong(100).msat, maxHtlc = Some((20000 + Random.nextLong(80000)).msat), CltvExpiryDelta(Random.nextInt(288)), capacity = (10 + Random.nextLong(100)).sat, balance_opt = Some(Random.nextLong(2 * amount.toLong).msat)), makeEdge(2L, d, a, Random.nextLong(250).msat, Random.nextInt(10000), minHtlc = Random.nextLong(100).msat, maxHtlc = Some((20000 + Random.nextLong(80000)).msat), CltvExpiryDelta(Random.nextInt(288)), capacity = (10 + Random.nextLong(100)).sat, balance_opt = Some(Random.nextLong(2 * amount.toLong).msat)), makeEdge(3L, d, e, Random.nextLong(250).msat, Random.nextInt(10000), minHtlc = Random.nextLong(100).msat, maxHtlc = Some((20000 + Random.nextLong(80000)).msat), CltvExpiryDelta(Random.nextInt(288)), capacity = (10 + Random.nextLong(100)).sat, balance_opt = Some(Random.nextLong(2 * amount.toLong).msat)), @@ -1590,7 +1591,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(7L, e, b, Random.nextLong(250).msat, Random.nextInt(10000), minHtlc = Random.nextLong(100).msat, maxHtlc = Some((20000 + Random.nextLong(80000)).msat), CltvExpiryDelta(Random.nextInt(288)), capacity = (10 + Random.nextLong(100)).sat), makeEdge(8L, b, c, Random.nextLong(250).msat, Random.nextInt(10000), minHtlc = Random.nextLong(100).msat, maxHtlc = Some((20000 + Random.nextLong(80000)).msat), CltvExpiryDelta(Random.nextInt(288)), capacity = (10 + Random.nextLong(100)).sat), makeEdge(9L, c, f, Random.nextLong(250).msat, Random.nextInt(10000), minHtlc = Random.nextLong(100).msat, maxHtlc = Some((20000 + Random.nextLong(80000)).msat), CltvExpiryDelta(Random.nextInt(288)), capacity = (10 + Random.nextLong(100)).sat) - )) + )), 1 day) findMultiPartRoute(g, d, f, amount, maxFee, routeParams = DEFAULT_ROUTE_PARAMS.copy(randomize = true), currentBlockHeight = BlockHeight(400000)) match { case Success(routes) => checkRouteAmounts(routes, amount, maxFee) @@ -1607,7 +1608,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // ^ | // | | // F <---+ - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 1000 msat, 1000), makeEdge(2L, b, c, 1000 msat, 1000), makeEdge(3L, c, d, 1000 msat, 1000), @@ -1615,7 +1616,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(5L, b, e, 1000 msat, 1000), makeEdge(6L, c, f, 1000 msat, 1000), makeEdge(7L, f, b, 1000 msat, 1000), - )) + )), 1 day) val Success(routes) = findRoute(g, a, e, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 3, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(routes.length == 2) @@ -1632,7 +1633,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // | ^ // | | // F ----+ - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, b, a, 1000 msat, 1000), makeEdge(2L, c, b, 1000 msat, 1000), makeEdge(3L, d, c, 1000 msat, 1000), @@ -1640,7 +1641,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(5L, e, b, 1000 msat, 1000), makeEdge(6L, f, c, 1000 msat, 1000), makeEdge(7L, b, f, 1000 msat, 1000), - )) + )), 1 day) val Success(routes) = findRoute(g, e, a, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 3, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(routes.length == 2) @@ -1676,7 +1677,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { q.toSeq } - val g = DirectedGraph(makeEdges(10)) + val g = GraphWithBalanceEstimates(DirectedGraph(makeEdges(10)), 1 day) val Success(routes) = findRoute(g, a, b, DEFAULT_AMOUNT_MSAT, 100000000 msat, numRoutes = 10, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(routes.distinct.length == 10) @@ -1708,7 +1709,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { q.toSeq } - val g = DirectedGraph(makeEdges(10)) + val g = GraphWithBalanceEstimates(DirectedGraph(makeEdges(10)), 1 day) val Success(routes) = findRoute(g, a, b, DEFAULT_AMOUNT_MSAT, 100000000 msat, numRoutes = 10, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) assert(routes.distinct.length == 10) @@ -1717,9 +1718,9 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { } test("can't relay if fee is not sufficient") { - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 1000 msat, 7000), - )) + )), 1 day) assert(findRoute(g, a, b, 10000000 msat, 10000 msat, numRoutes = 3, routeParams = DEFAULT_ROUTE_PARAMS.copy(includeLocalChannelCost = true), currentBlockHeight = BlockHeight(400000)) == Failure(RouteNotFound)) assert(findRoute(g, a, b, 10000000 msat, 100000 msat, numRoutes = 3, routeParams = DEFAULT_ROUTE_PARAMS.copy(includeLocalChannelCost = true), currentBlockHeight = BlockHeight(400000)).isSuccess) @@ -1734,7 +1735,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // v | // E ---> F val start = randomKey().publicKey - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(0L, start, a, 0 msat, 0), makeEdge(1L, a, b, 1000 msat, 1000), makeEdge(2L, a, c, 0 msat, 0), @@ -1743,7 +1744,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { makeEdge(5L, c, e, 0 msat, 0), makeEdge(6L, e, f, 600 msat, 1000), makeEdge(7L, f, d, 0 msat, 0), - )) + )), 1 day) { // No hop cost val Success(routes) = findRoute(g, start, b, DEFAULT_AMOUNT_MSAT, 100000000 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS, currentBlockHeight = BlockHeight(400000)) @@ -1771,13 +1772,13 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // v | // C ---> D val start = randomKey().publicKey - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(0L, start, a, 0 msat, 0), makeEdge(1L, a, b, 1000 msat, 1000, capacity = (DEFAULT_AMOUNT_MSAT * 1.2).truncateToSatoshi), makeEdge(2L, a, c, 400 msat, 500, capacity = (DEFAULT_AMOUNT_MSAT * 3).truncateToSatoshi), makeEdge(3L, c, d, 400 msat, 500, capacity = (DEFAULT_AMOUNT_MSAT * 3).truncateToSatoshi), makeEdge(4L, d, b, 400 msat, 500, capacity = (DEFAULT_AMOUNT_MSAT * 3).truncateToSatoshi), - )) + )), 1 day) { val hc = HeuristicsConstants( @@ -1785,6 +1786,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { failureCost = RelayFees(1000 msat, 500), hopCost = RelayFees(0 msat, 0), useLogProbability = false, + usePastRelaysData = true, ) val Success(routes) = findRoute(g, start, b, DEFAULT_AMOUNT_MSAT, 100000000 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = Right(hc)), currentBlockHeight = BlockHeight(400000)) assert(routes.distinct.length == 1) @@ -1799,6 +1801,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { failureCost = RelayFees(10000 msat, 1000), hopCost = RelayFees(0 msat, 0), useLogProbability = true, + usePastRelaysData = true, ) val Success(routes) = findRoute(g, start, b, DEFAULT_AMOUNT_MSAT, 100000000 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = Right(hc)), currentBlockHeight = BlockHeight(400000)) assert(routes.distinct.length == 1) @@ -1813,19 +1816,20 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // v | // C ---> D val start = randomKey().publicKey - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(0L, start, a, 0 msat, 0), makeEdge(1L, a, b, 1000 msat, 1000, cltvDelta = CltvExpiryDelta(1000)), makeEdge(2L, a, c, 350 msat, 350, cltvDelta = CltvExpiryDelta(10)), makeEdge(3L, c, d, 350 msat, 350, cltvDelta = CltvExpiryDelta(10)), makeEdge(4L, d, b, 350 msat, 350, cltvDelta = CltvExpiryDelta(10)), - )) + )), 1 day) val hc = HeuristicsConstants( lockedFundsRisk = 1e-7, failureCost = RelayFees(0 msat, 0), hopCost = RelayFees(0 msat, 0), useLogProbability = true, + usePastRelaysData = true, ) val Success(routes) = findRoute(g, start, b, DEFAULT_AMOUNT_MSAT, 100000000 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = Right(hc)), currentBlockHeight = BlockHeight(400000)) assert(routes.distinct.length == 1) @@ -1835,17 +1839,18 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("edge too small to relay payment is ignored") { // A ===> B ===> C <--- D - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 100 msat, 100), makeEdge(2L, b, c, 100 msat, 100), makeEdge(3L, d, c, 100 msat, 100, capacity = 1000 sat), - )) + )), 1 day) val hc = HeuristicsConstants( lockedFundsRisk = 1e-7, failureCost = RelayFees(0 msat, 0), hopCost = RelayFees(0 msat, 0), useLogProbability = true, + usePastRelaysData = true, ) val Success(routes) = findRoute(g, a, c, DEFAULT_AMOUNT_MSAT, 100000000 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = Right(hc)), currentBlockHeight = BlockHeight(400000)) assert(routes.distinct.length == 1) @@ -1857,11 +1862,11 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // A ===> B ===> C // \___________/ val recentChannelId = ShortChannelId.fromCoordinates("399990x1x2").success.value.toLong - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 1 msat, 1, capacity = 100_000_000 sat), makeEdge(2L, b, c, 1 msat, 1, capacity = 100_000_000 sat), makeEdge(recentChannelId, a, c, 1000 msat, 100), - )) + )), 1 day) val wr = WeightRatios( baseFactor = 0, @@ -1878,7 +1883,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("trampoline relay with direct channel to target") { val amount = 100_000_000 msat - val g = DirectedGraph(List(makeEdge(1L, a, b, 1000 msat, 1000, capacity = 100_000_000 sat))) + val g = GraphWithBalanceEstimates(DirectedGraph(List(makeEdge(1L, a, b, 1000 msat, 1000, capacity = 100_000_000 sat))), 1 day) { val routeParams = DEFAULT_ROUTE_PARAMS.copy(includeLocalChannelCost = true, boundaries = SearchBoundaries(100_999 msat, 0.0, 6, CltvExpiryDelta(576))) @@ -1893,12 +1898,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("small local edge with liquidity is better than big remote edge") { // A == B == C -- D // \_______/ - val g = DirectedGraph(List( + val g = GraphWithBalanceEstimates(DirectedGraph(List( makeEdge(1L, a, b, 100 msat, 100, minHtlc = 1000 msat, capacity = 100000000 sat, balance_opt = Some(10000000 msat)), makeEdge(2L, b, c, 100 msat, 100, minHtlc = 1000 msat, capacity = 100000000 sat), makeEdge(3L, a, c, 100 msat, 100, minHtlc = 1000 msat, capacity = 100 sat, balance_opt = Some(100000 msat)), makeEdge(4L, c, d, 100 msat, 100, minHtlc = 1000 msat, capacity = 100000000 sat), - )) + )), 1 day) val wr = WeightRatios( baseFactor = 0, From 62dc7465e7b60c06c0eee038c786c51c7689f12b Mon Sep 17 00:00:00 2001 From: Thomas HUET Date: Mon, 21 Aug 2023 13:46:31 +0200 Subject: [PATCH 2/2] test --- .../eclair/router/RouteCalculationSpec.scala | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala index d023c81b04..e89094bb28 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala @@ -1916,6 +1916,38 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { val route :: Nil = routes assert(route2Ids(route) == 3 :: 4 :: Nil) } + + test("take past attempts into account") { + // C + // / \ + // A -- B E + // \ / + // D + val g = GraphWithBalanceEstimates(DirectedGraph(List( + makeEdge(1L, a, b, 100 msat, 100, minHtlc = 1000 msat, capacity = 100000000 sat), + makeEdge(2L, b, c, 100 msat, 100, minHtlc = 1000 msat, capacity = 100000000 sat), + makeEdge(3L, c, e, 100 msat, 100, minHtlc = 1000 msat, capacity = 100000000 sat), + makeEdge(4L, b, d, 1000 msat, 1000, minHtlc = 1000 msat, capacity = 100000 sat), + makeEdge(5L, d, e, 1000 msat, 1000, minHtlc = 1000 msat, capacity = 100000 sat), + )), 1 day) + + val amount = 50000 msat + + val hc = HeuristicsConstants( + lockedFundsRisk = 0, + failureCost = RelayFees(1000 msat, 1000), + hopCost = RelayFees(500 msat, 200), + useLogProbability = true, + usePastRelaysData = true + ) + val Success(route1 :: Nil) = findRoute(g, a, e, amount, 100000000 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = Right(hc), includeLocalChannelCost = true), currentBlockHeight = BlockHeight(400000)) + assert(route2Ids(route1) == 1 :: 2 :: 3 :: Nil) + + val h = g.routeCouldRelay(route1.stopAt(c)).channelCouldNotSend(route1.hops.last, amount) + + val Success(route2 :: Nil) = findRoute(h, a, e, amount, 100000000 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = Right(hc), includeLocalChannelCost = true), currentBlockHeight = BlockHeight(400000)) + assert(route2Ids(route2) == 1 :: 4 :: 5 :: Nil) + } } object RouteCalculationSpec {