Skip to content

Commit 4e724ec

Browse files
committed
Use balance estimates from past payments in path-finding
1 parent c4786f3 commit 4e724ec

File tree

9 files changed

+258
-240
lines changed

9 files changed

+258
-240
lines changed

eclair-core/src/main/resources/reference.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,8 @@ eclair {
281281
// probability of success, however is penalizes less the paths with a low probability of success.
282282
use-log-probability = false
283283

284+
use-past-relay-data = false
285+
284286
mpp {
285287
min-amount-satoshis = 15000 // minimum amount sent via partial HTLCs
286288
max-parts = 5 // maximum number of HTLCs sent per payment: increasing this value will impact performance

eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ object NodeParams extends Logging {
366366
failureCost = getRelayFees(config.getConfig("failure-cost")),
367367
hopCost = getRelayFees(config.getConfig("hop-cost")),
368368
useLogProbability = config.getBoolean("use-log-probability"),
369+
usePastRelaysData = config.getBoolean("use-past-relay-data"),
369370
))
370371
},
371372
mpp = MultiPartParams(

eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ object EclairInternalsSerializer {
6868
("lockedFundsRisk" | double) ::
6969
("failureCost" | relayFeesCodec) ::
7070
("hopCost" | relayFeesCodec) ::
71-
("useLogProbability" | bool(8))).as[HeuristicsConstants]
71+
("useLogProbability" | bool(8)) ::
72+
("usePastRelaysData" | bool(8))).as[HeuristicsConstants]
7273

7374
val multiPartParamsCodec: Codec[MultiPartParams] = (
7475
("minPartAmount" | millisatoshi) ::

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

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ object BalanceEstimate {
234234
case class BalancesEstimates(balances: Map[(PublicKey, PublicKey), BalanceEstimate], defaultHalfLife: FiniteDuration) {
235235
private def get(a: PublicKey, b: PublicKey): Option[BalanceEstimate] = balances.get((a, b))
236236

237+
def get(edge: GraphEdge): BalanceEstimate = get(edge.desc.a, edge.desc.b).getOrElse(BalanceEstimate.empty(defaultHalfLife).addEdge(edge))
238+
237239
def addEdge(edge: GraphEdge): BalancesEstimates = BalancesEstimates(
238240
balances.updatedWith((edge.desc.a, edge.desc.b))(balance =>
239241
Some(balance.getOrElse(BalanceEstimate.empty(defaultHalfLife)).addEdge(edge))
@@ -283,7 +285,7 @@ case class BalancesEstimates(balances: Map[(PublicKey, PublicKey), BalanceEstima
283285

284286
}
285287

286-
case class GraphWithBalanceEstimates(graph: DirectedGraph, private val balances: BalancesEstimates) {
288+
case class GraphWithBalanceEstimates(graph: DirectedGraph, balances: BalancesEstimates) {
287289
def addEdge(edge: GraphEdge): GraphWithBalanceEstimates = GraphWithBalanceEstimates(graph.addEdge(edge), balances.addEdge(edge))
288290

289291
def removeEdge(desc: ChannelDesc): GraphWithBalanceEstimates = GraphWithBalanceEstimates(graph.removeEdge(desc), balances.removeEdge(desc))
@@ -312,13 +314,6 @@ case class GraphWithBalanceEstimates(graph: DirectedGraph, private val balances:
312314
def channelCouldNotSend(hop: ChannelHop, amount: MilliSatoshi): GraphWithBalanceEstimates = {
313315
GraphWithBalanceEstimates(graph, balances.channelCouldNotSend(hop, amount))
314316
}
315-
316-
def canSend(amount: MilliSatoshi, edge: GraphEdge): Double = {
317-
balances.balances.get((edge.desc.a, edge.desc.b)) match {
318-
case Some(estimate) => estimate.canSend(amount)
319-
case None => BalanceEstimate.empty(1 hour).addEdge(edge).canSend(amount)
320-
}
321-
}
322317
}
323318

324319
object GraphWithBalanceEstimates {

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

Lines changed: 66 additions & 54 deletions
Large diffs are not rendered by default.

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ object RouteCalculation {
120120
val tags = TagSet.Empty.withTag(Tags.MultiPart, r.allowMultiPart).withTag(Tags.Amount, Tags.amountBucket(r.amount))
121121
KamonExt.time(Metrics.FindRouteDuration.withTags(tags.withTag(Tags.NumberOfRoutes, routesToFind.toLong))) {
122122
val result = if (r.allowMultiPart) {
123-
findMultiPartRoute(d.graphWithBalances.graph, r.source, r.target, r.amount, r.maxFee, extraEdges, ignoredEdges, r.ignore.nodes, r.pendingPayments, params, currentBlockHeight)
123+
findMultiPartRoute(d.graphWithBalances, r.source, r.target, r.amount, r.maxFee, extraEdges, ignoredEdges, r.ignore.nodes, r.pendingPayments, params, currentBlockHeight)
124124
} else {
125-
findRoute(d.graphWithBalances.graph, r.source, r.target, r.amount, r.maxFee, routesToFind, extraEdges, ignoredEdges, r.ignore.nodes, params, currentBlockHeight)
125+
findRoute(d.graphWithBalances, r.source, r.target, r.amount, r.maxFee, routesToFind, extraEdges, ignoredEdges, r.ignore.nodes, params, currentBlockHeight)
126126
}
127127
result match {
128128
case Success(routes) =>
@@ -204,7 +204,7 @@ object RouteCalculation {
204204
* @param routeParams a set of parameters that can restrict the route search
205205
* @return the computed routes to the destination @param targetNodeId
206206
*/
207-
def findRoute(g: DirectedGraph,
207+
def findRoute(g: GraphWithBalanceEstimates,
208208
localNodeId: PublicKey,
209209
targetNodeId: PublicKey,
210210
amount: MilliSatoshi,
@@ -222,7 +222,7 @@ object RouteCalculation {
222222
}
223223

224224
@tailrec
225-
private def findRouteInternal(g: DirectedGraph,
225+
private def findRouteInternal(g: GraphWithBalanceEstimates,
226226
localNodeId: PublicKey,
227227
targetNodeId: PublicKey,
228228
amount: MilliSatoshi,
@@ -280,7 +280,7 @@ object RouteCalculation {
280280
* @param routeParams a set of parameters that can restrict the route search
281281
* @return a set of disjoint routes to the destination @param targetNodeId with the payment amount split between them
282282
*/
283-
def findMultiPartRoute(g: DirectedGraph,
283+
def findMultiPartRoute(g: GraphWithBalanceEstimates,
284284
localNodeId: PublicKey,
285285
targetNodeId: PublicKey,
286286
amount: MilliSatoshi,
@@ -304,7 +304,7 @@ object RouteCalculation {
304304
}
305305
}
306306

307-
private def findMultiPartRouteInternal(g: DirectedGraph,
307+
private def findMultiPartRouteInternal(g: GraphWithBalanceEstimates,
308308
localNodeId: PublicKey,
309309
targetNodeId: PublicKey,
310310
amount: MilliSatoshi,
@@ -319,7 +319,7 @@ object RouteCalculation {
319319
// When the recipient is a direct peer, we have complete visibility on our local channels so we can use more accurate MPP parameters.
320320
val routeParams1 = {
321321
case class DirectChannel(balance: MilliSatoshi, isEmpty: Boolean)
322-
val directChannels = g.getEdgesBetween(localNodeId, targetNodeId).collect {
322+
val directChannels = g.graph.getEdgesBetween(localNodeId, targetNodeId).collect {
323323
// We should always have balance information available for local channels.
324324
// NB: htlcMinimumMsat is set by our peer and may be 0 msat (even though it's not recommended).
325325
case GraphEdge(_, params, _, Some(balance)) => DirectChannel(balance, balance <= 0.msat || balance < params.htlcMinimum)

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -277,16 +277,16 @@ class BalanceEstimateSpec extends AnyFunSuite {
277277
val edge_ab = makeEdge(a, b, 1, 10 sat)
278278
val edge_ba = makeEdge(b, a, 1, 10 sat)
279279
val edge_bc = makeEdge(b, c, 6, 10 sat)
280-
assert(graphWithBalances.canSend(27500 msat, edge_ab) === 0.75 +- 0.01)
281-
assert(graphWithBalances.canSend(55000 msat, edge_ab) === 0.5 +- 0.01)
282-
assert(graphWithBalances.canSend(30000 msat, edge_ba) === 0.75 +- 0.01)
283-
assert(graphWithBalances.canSend(60000 msat, edge_ba) === 0.5 +- 0.01)
284-
assert(graphWithBalances.canSend(75000 msat, edge_bc) === 0.5 +- 0.01)
285-
assert(graphWithBalances.canSend(100000 msat, edge_bc) === 0.33 +- 0.01)
280+
assert(graphWithBalances.balances.get(edge_ab).canSend(27500 msat) === 0.75 +- 0.01)
281+
assert(graphWithBalances.balances.get(edge_ab).canSend(55000 msat) === 0.5 +- 0.01)
282+
assert(graphWithBalances.balances.get(edge_ba).canSend(30000 msat) === 0.75 +- 0.01)
283+
assert(graphWithBalances.balances.get(edge_ba).canSend(60000 msat) === 0.5 +- 0.01)
284+
assert(graphWithBalances.balances.get(edge_bc).canSend(75000 msat) === 0.5 +- 0.01)
285+
assert(graphWithBalances.balances.get(edge_bc).canSend(100000 msat) === 0.33 +- 0.01)
286286
val unknownEdge = makeEdge(42, 40 sat)
287-
assert(graphWithBalances.canSend(10000 msat, unknownEdge) === 0.75 +- 0.01)
288-
assert(graphWithBalances.canSend(20000 msat, unknownEdge) === 0.5 +- 0.01)
289-
assert(graphWithBalances.canSend(30000 msat, unknownEdge) === 0.25 +- 0.01)
287+
assert(graphWithBalances.balances.get(unknownEdge).canSend(10000 msat) === 0.75 +- 0.01)
288+
assert(graphWithBalances.balances.get(unknownEdge).canSend(20000 msat) === 0.5 +- 0.01)
289+
assert(graphWithBalances.balances.get(unknownEdge).canSend(30000 msat) === 0.25 +- 0.01)
290290
}
291291

292292
}

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, ShortChannelId}
2727
import org.scalatest.funsuite.AnyFunSuite
2828
import scodec.bits._
2929

30+
import scala.concurrent.duration.DurationInt
31+
3032
class GraphSpec extends AnyFunSuite {
3133

3234
val (a, b, c, d, e, f, g) = (
@@ -167,8 +169,8 @@ class GraphSpec extends AnyFunSuite {
167169

168170
val bIncoming = graph.getIncomingEdgesOf(b)
169171
assert(bIncoming.size == 1)
170-
assert(bIncoming.exists(_.desc.a == a)) // there should be an edge a --> b
171-
assert(bIncoming.exists(_.desc.b == b))
172+
assert(bIncoming(a).exists(_.desc.a == a)) // there should be an edge a --> b
173+
assert(bIncoming(a).exists(_.desc.b == b))
172174

173175
val bOutgoing = graph.edgesOf(b)
174176
assert(bOutgoing.size == 1)
@@ -220,18 +222,18 @@ class GraphSpec extends AnyFunSuite {
220222
val graph = DirectedGraph(Seq(edgeAB, edgeAD, edgeBC, edgeDC))
221223

222224
assert(graph.edgesOf(a).toSet == Set(edgeAB, edgeAD))
223-
assert(graph.getIncomingEdgesOf(a) == Nil)
225+
assert(graph.getIncomingEdgesOf(a).isEmpty)
224226
assert(graph.edgesOf(c) == Nil)
225-
assert(graph.getIncomingEdgesOf(c).toSet == Set(edgeBC, edgeDC))
227+
assert(graph.getIncomingEdgesOf(c).values.flatten.toSet == Set(edgeBC, edgeDC))
226228

227229
val edgeAB1 = edgeAB.copy(balance_opt = Some(200000 msat))
228230
val edgeBC1 = edgeBC.copy(balance_opt = Some(150000 msat))
229231
val graph1 = graph.addEdge(edgeAB1).addEdge(edgeBC1)
230232

231233
assert(graph1.edgesOf(a).toSet == Set(edgeAB1, edgeAD))
232-
assert(graph1.getIncomingEdgesOf(a) == Nil)
234+
assert(graph1.getIncomingEdgesOf(a).isEmpty)
233235
assert(graph1.edgesOf(c) == Nil)
234-
assert(graph1.getIncomingEdgesOf(c).toSet == Set(edgeBC1, edgeDC))
236+
assert(graph1.getIncomingEdgesOf(c).values.flatten.toSet == Set(edgeBC1, edgeDC))
235237
}
236238

237239
def descFromNodes(shortChannelId: Long, a: PublicKey, b: PublicKey): ChannelDesc = makeEdge(shortChannelId, a, b, 0 msat, 0).desc
@@ -255,9 +257,9 @@ class GraphSpec extends AnyFunSuite {
255257
val edgeDE = makeEdge(6L, d, e, 9 msat, 0, capacity = 200000 sat)
256258
val graph = DirectedGraph(Seq(edgeAB, edgeBC, edgeCD, edgeDC, edgeCE, edgeDE))
257259

258-
val path :: Nil = yenKshortestPaths(graph, a, e, 100000000 msat,
260+
val path :: Nil = yenKshortestPaths(GraphWithBalanceEstimates(graph, 1 day), a, e, 100000000 msat,
259261
Set.empty, Set.empty, Set.empty, 1,
260-
Right(HeuristicsConstants(1.0E-8, RelayFees(2000 msat, 500), RelayFees(50 msat, 20), useLogProbability = true)),
262+
Right(HeuristicsConstants(1.0E-8, RelayFees(2000 msat, 500), RelayFees(50 msat, 20), useLogProbability = true, usePastRelaysData = false)),
261263
BlockHeight(714930), _ => true, includeLocalChannelCost = true)
262264
assert(path.path == Seq(edgeAB, edgeBC, edgeCE))
263265
}

0 commit comments

Comments
 (0)