Remove `PaymentWeightRatios` from the routing config (#3171)
What changed, and why it matters
This commit removes an older, less effective way of choosing Lightning payment routes (called PaymentWeightRatios) and switches the code to use a newer route-scoring system based on estimated success probability. It is a routine cleanup/refactoring change, not a security fix. There is no evidence in the commit message or diff of a vulnerability being patched.
No security action required. Treat as normal maintenance/refactoring commit. Reviewers may want to confirm that existing deployments with legacy ratios config entries fail gracefully or are ignored, since the option is removed.
Security signals we found
No security-relevant code change identified
Removal of deprecated routing heuristic configuration option
Test-only and configuration-only refactor
Evidence from the diff
The change deletes the PaymentWeightRatios case class and its associated routing-weight calculation, removes the use-ratios configuration toggle, and makes HeuristicsConstants the only active weighting strategy. It updates reference.conf, NodeParams parsing, the remote serializer codec, and all tests that previously constructed PaymentWeightRatios. The diff shows straightforward removal of dead code and migration of test fixtures to the new constants; no input validation, cryptographic, or network-handling logic is altered in a security-relevant way.
Changed components
eclair-core router path-finding configurationeclair-core Graph routing heuristicsEclairInternalsSerializerNodeParams config parserRouting-related unit and integration testsInspect captured patch +86 / −279
diff --git a/docs/Configure.md b/docs/Configure.md
index 3e36266..1c5e596 100644
--- a/docs/Configure.md
+++ b/docs/Configure.md
@@ -261,32 +261,16 @@ eclair {
path-finding {
experiments {
control = ${eclair.router.path-finding.default} {
- percentage = 50
- }
-
- // alternative routing heuristics (replaces ratios)
- test-failure-cost = ${eclair.router.path-finding.default} {
- use-ratios = false
-
- locked-funds-risk = 1e-8 // msat per msat locked per block. It should be your expected interest rate per block multiplied by the probability that something goes wrong and your funds stay locked.
- // 1e-8 corresponds to an interest rate of ~5% per year (1e-6 per block) and a probability of 1% that the channel will fail and our funds will be locked.
-
- // Virtual fee for failed payments
- // Corresponds to how much you are willing to pay to get one less failed payment attempt
- failure-cost {
- fee-base-msat = 2000
- fee-proportional-millionths = 500
- }
- percentage = 10
+ percentage = 70
}
// To optimize for fees only:
test-fees-only = ${eclair.router.path-finding.default} {
- ratios {
- base = 1
- cltv = 0
- channel-age = 0
- channel-capacity = 0
+ // By setting everything to zero, only fees will be taken into account.
+ locked-funds-risk = 0
+ failure-cost {
+ fee-base-msat = 0
+ fee-proportional-millionths = 0
}
hop-cost {
fee-base-msat = 0
@@ -297,12 +281,6 @@ eclair {
// To optimize for shorter paths:
test-short-paths = ${eclair.router.path-finding.default} {
- ratios {
- base = 1
- cltv = 0
- channel-age = 0
- channel-capacity = 0
- }
hop-cost {
// High hop cost penalizes strongly longer paths
fee-base-msat = 10000
@@ -313,30 +291,8 @@ eclair {
// To optimize for successful payments:
test-pay-safe = ${eclair.router.path-finding.default} {
- ratios {
- base = 0
- cltv = 0
- channel-age = 0.5 // Old channels should have less risk of failures
- channel-capacity = 0.5 // High capacity channels are more likely to have enough liquidity to relay our payment
- }
- hop-cost {
- // Less hops means less chances of failures
- fee-base-msat = 1000
- fee-proportional-millionths = 1000
- }
- percentage = 10
- }
-
- // To optimize for fast payments:
- test-pay-fast = ${eclair.router.path-finding.default} {
- ratios {
- base = 0.2
- cltv = 0.5 // In case of failure we want our funds back as fast as possible
- channel-age = 0.3 // Older channels are more likely to run smoothly
- channel-capacity = 0
- }
- hop-cost {
- // Shorter paths should be faster
+ failure-cost {
+ // High failure cost will penalize paths that are less likely to succeed.
fee-base-msat = 10000
fee-proportional-millionths = 10000
}
diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf
index f509dda..4bfe117 100644
--- a/eclair-core/src/main/resources/reference.conf
+++ b/eclair-core/src/main/resources/reference.conf
@@ -477,19 +477,6 @@ eclair {
fee-proportional-millionths = 200
}
- use-ratios = true // if true, will use `ratios`, if false, will use `failure-cost`, `locked-funds-risk`, `use-log-probability`, `use-past-relay-data`.
-
- // channel 'weight' is computed with the following formula: (channelFee + hop-cost) * (ratio-base + cltvDelta * ratio-cltv + channelAge * ratio-channel-age + channelCapacity * ratio-channel-capacity)
- // the following parameters can be used to ask the router to use heuristics to find i.e: 'cltv-optimized' routes, **the sum of the four ratios must be 1**
- ratios {
- base = 0.0
- cltv = 0.05 // when computing the weight for a channel, consider its CLTV delta in this proportion
- channel-age = 0.4 // when computing the weight for a channel, consider its AGE in this proportion
- channel-capacity = 0.55 // when computing the weight for a channel, consider its CAPACITY in this proportion
- }
-
- // Everything below is ignored if use-ratio = true.
-
locked-funds-risk = 1e-8 // msat per msat locked per block. It should be your expected interest rate per block multiplied by the probability that something goes wrong and your funds stay locked.
// 1e-8 corresponds to an interest rate of ~5% per year (1e-6 per block) and a probability of 1% that the channel will fail and our funds will be locked.
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 b583332..733155b 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
@@ -35,7 +35,7 @@ import fr.acinq.eclair.payment.relay.OnTheFlyFunding
import fr.acinq.eclair.payment.relay.Relayer.{AsyncPaymentsParams, RelayFees, RelayParams}
import fr.acinq.eclair.reputation.Reputation
import fr.acinq.eclair.router.Announcements.AddressException
-import fr.acinq.eclair.router.Graph.{HeuristicsConstants, PaymentWeightRatios}
+import fr.acinq.eclair.router.Graph.HeuristicsConstants
import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.router.{Graph, PathFindingExperimentConf, Router}
import fr.acinq.eclair.tor.Socks5ProxyParams
@@ -460,23 +460,12 @@ object NodeParams extends Logging {
maxCltv = CltvExpiryDelta(config.getInt("boundaries.max-cltv")),
maxFeeFlat = Satoshi(config.getLong("boundaries.max-fee-flat-sat")).toMilliSatoshi,
maxFeeProportional = config.getDouble("boundaries.max-fee-proportional-percent") / 100.0),
- heuristics = if (config.getBoolean("use-ratios")) {
- PaymentWeightRatios(
- baseFactor = config.getDouble("ratios.base"),
- cltvDeltaFactor = config.getDouble("ratios.cltv"),
- ageFactor = config.getDouble("ratios.channel-age"),
- capacityFactor = config.getDouble("ratios.channel-capacity"),
- hopFees = getRelayFees(config.getConfig("hop-cost")),
- )
- } else {
- HeuristicsConstants(
- lockedFundsRisk = config.getDouble("locked-funds-risk"),
- failureFees = getRelayFees(config.getConfig("failure-cost")),
- hopFees = getRelayFees(config.getConfig("hop-cost")),
- useLogProbability = config.getBoolean("use-log-probability"),
- usePastRelaysData = config.getBoolean("use-past-relay-data"),
- )
- },
+ heuristics = HeuristicsConstants(
+ lockedFundsRisk = config.getDouble("locked-funds-risk"),
+ failureFees = getRelayFees(config.getConfig("failure-cost")),
+ hopFees = getRelayFees(config.getConfig("hop-cost")),
+ useLogProbability = config.getBoolean("use-log-probability"),
+ usePastRelaysData = config.getBoolean("use-past-relay-data")),
mpp = MultiPartParams(
Satoshi(config.getLong("mpp.min-amount-satoshis")).toMilliSatoshi,
config.getInt("mpp.max-parts")),
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 6c26610..10336d0 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
@@ -24,7 +24,7 @@ import fr.acinq.eclair.io.Peer.PeerRoutingMessage
import fr.acinq.eclair.io.Switchboard.RouterPeerConf
import fr.acinq.eclair.io.{ClientSpawner, Peer, PeerConnection, Switchboard}
import fr.acinq.eclair.payment.relay.Relayer.RelayFees
-import fr.acinq.eclair.router.Graph.{HeuristicsConstants, PaymentPathWeight, PaymentWeightRatios, WeightRatios}
+import fr.acinq.eclair.router.Graph.{HeuristicsConstants, PaymentPathWeight, WeightRatios}
import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.router._
import fr.acinq.eclair.wire.protocol.CommonCodecs._
@@ -57,13 +57,6 @@ object EclairInternalsSerializer {
("feeBase" | millisatoshi) ::
("feeProportionalMillionths" | int64)).as[RelayFees]
- val paymentWeightRatiosCodec: Codec[PaymentWeightRatios] = (
- ("baseFactor" | double) ::
- ("cltvDeltaFactor" | double) ::
- ("ageFactor" | double) ::
- ("capacityFactor" | double) ::
- ("hopCost" | relayFeesCodec)).as[PaymentWeightRatios]
-
val heuristicsConstantsCodec: Codec[HeuristicsConstants] = (
("lockedFundsRisk" | double) ::
("failureCost" | relayFeesCodec) ::
@@ -73,7 +66,6 @@ object EclairInternalsSerializer {
val weightRatiosCodec: Codec[WeightRatios[PaymentPathWeight]] =
discriminated[WeightRatios[PaymentPathWeight]].by(uint8)
- .typecase(0x00, paymentWeightRatiosCodec)
.typecase(0xff, heuristicsConstantsCodec)
val multiPartParamsCodec: Codec[MultiPartParams] = (
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 c650d0a..822e163 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
@@ -87,56 +87,6 @@ object Graph {
def addEdgeWeight(sender: PublicKey, edge: GraphEdge, balance: BalanceEstimate, prev: RichWeight, currentBlockHeight: BlockHeight, includeLocalChannelCost: Boolean): RichWeight
}
- /**
- * We use heuristics to calculate the weight of an edge based on channel age, cltv delta, capacity and a virtual hop cost to keep routes short.
- * We favor older channels, with bigger capacity and small cltv delta.
- */
- case class PaymentWeightRatios(baseFactor: Double, cltvDeltaFactor: Double, ageFactor: Double, capacityFactor: Double, hopFees: RelayFees) extends WeightRatios[PaymentPathWeight] {
- require(baseFactor + cltvDeltaFactor + ageFactor + capacityFactor == 1, "The sum of heuristics ratios must be 1")
- require(baseFactor >= 0.0, "ratio-base must be nonnegative")
- require(cltvDeltaFactor >= 0.0, "ratio-cltv must be nonnegative")
- require(ageFactor >= 0.0, "ratio-channel-age must be nonnegative")
- require(capacityFactor >= 0.0, "ratio-channel-capacity must be nonnegative")
-
- override def addEdgeWeight(sender: PublicKey, edge: GraphEdge, balance: BalanceEstimate, prev: PaymentPathWeight, currentBlockHeight: BlockHeight, includeLocalChannelCost: Boolean): PaymentPathWeight = {
- 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
- val totalCltv = prev.cltv + edge.params.cltvExpiryDelta
- val hopCost = if (edge.desc.a == sender) 0 msat else nodeFee(hopFees, prev.amount)
- import RoutingHeuristics._
-
- // Every edge is weighted by funding block height where older blocks add less weight. The window considered is 1 year.
- val ageFactor = edge.desc.shortChannelId match {
- case real: RealShortChannelId => normalize(real.blockHeight.toDouble, min = (currentBlockHeight - BLOCK_TIME_ONE_YEAR).toDouble, max = currentBlockHeight.toDouble)
- // for local channels or route hints we don't easily have access to the channel block height, but we want to
- // give them the best score anyway
- case _: Alias => 1
- case _: UnspecifiedShortChannelId => 1
- }
-
- // Every edge is weighted by channel capacity, larger channels add less weight
- val edgeMaxCapacity = edge.capacity.toMilliSatoshi
- val capFactor =
- if (edge.balance_opt.isDefined) 0 // If we know the balance of the channel we treat it as if it had the maximum capacity.
- else 1 - normalize(edgeMaxCapacity.toLong.toDouble, CAPACITY_CHANNEL_LOW.toLong.toDouble, CAPACITY_CHANNEL_HIGH.toLong.toDouble)
-
- // Every edge is weighted by its cltv-delta value, normalized
- val cltvFactor = normalize(edge.params.cltvExpiryDelta.toInt, CLTV_LOW, CLTV_HIGH)
-
- // NB we're guaranteed to have weightRatios and factors > 0
- val factor = baseFactor + (cltvFactor * this.cltvDeltaFactor) + (ageFactor * this.ageFactor) + (capFactor * this.capacityFactor)
- val totalWeight = prev.weight + (fee + hopCost).toLong * factor
- val richWeight = PaymentPathWeight(totalAmount, prev.length + 1, totalCltv, 1.0, totalFees, 0 msat, totalWeight)
- if (edge.desc.a == sender) {
- // If this is a local channel it shouldn't add any weight. We always prefer local channels.
- richWeight.copy(weight = prev.weight)
- } else {
- richWeight
- }
- }
- }
-
/**
* We use heuristics to calculate the weight of an edge.
* The fee for a failed attempt and the fee per hop are never actually spent, they are used to incentivize shorter
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala
index 4f43e3a..e96cec0 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala
@@ -29,7 +29,7 @@ import fr.acinq.eclair.message.OnionMessages.OnionMessageConfig
import fr.acinq.eclair.payment.offer.OffersConfig
import fr.acinq.eclair.payment.relay.OnTheFlyFunding
import fr.acinq.eclair.payment.relay.Relayer.{AsyncPaymentsParams, RelayFees, RelayParams}
-import fr.acinq.eclair.router.Graph.{MessageWeightRatios, PaymentWeightRatios}
+import fr.acinq.eclair.router.Graph.{MessageWeightRatios, HeuristicsConstants}
import fr.acinq.eclair.reputation.Reputation
import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.router.{PathFindingExperimentConf, Router}
@@ -221,12 +221,12 @@ object TestConstants {
maxFeeProportional = 0.03,
maxCltv = CltvExpiryDelta(2016),
maxRouteLength = 20),
- heuristics = PaymentWeightRatios(
- baseFactor = 1.0,
- cltvDeltaFactor = 0.0,
- ageFactor = 0.0,
- capacityFactor = 0.0,
+ heuristics = HeuristicsConstants(
+ lockedFundsRisk = 0,
+ failureFees = RelayFees(0 msat, 0),
hopFees = RelayFees(0 msat, 0),
+ useLogProbability = false,
+ usePastRelaysData = false
),
mpp = MultiPartParams(
minPartAmount = 15000000 msat,
@@ -412,12 +412,12 @@ object TestConstants {
maxFeeProportional = 0.03,
maxCltv = CltvExpiryDelta(2016),
maxRouteLength = 20),
- heuristics = PaymentWeightRatios(
- baseFactor = 1.0,
- cltvDeltaFactor = 0.0,
- ageFactor = 0.0,
- capacityFactor = 0.0,
+ heuristics = HeuristicsConstants(
+ lockedFundsRisk = 0,
+ failureFees = RelayFees(0 msat, 0),
hopFees = RelayFees(0 msat, 0),
+ useLogProbability = false,
+ usePastRelaysData = false
),
mpp = MultiPartParams(
minPartAmount = 15000000 msat,
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala
index 36043db..a078ec6 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala
@@ -25,7 +25,7 @@ import fr.acinq.eclair.blockchain.bitcoind.BitcoindService
import fr.acinq.eclair.io.Peer.OpenChannelResponse
import fr.acinq.eclair.io.{Peer, PeerConnection}
import fr.acinq.eclair.payment.relay.Relayer.RelayFees
-import fr.acinq.eclair.router.Graph.PaymentWeightRatios
+import fr.acinq.eclair.router.Graph.HeuristicsConstants
import fr.acinq.eclair.router.RouteCalculation.ROUTE_MAX_LENGTH
import fr.acinq.eclair.router.Router.{MultiPartParams, PathFindingConf, SearchBoundaries, NORMAL => _, State => _}
import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Kit, MilliSatoshi, MilliSatoshiLong, Setup, TestKitBaseClass}
@@ -57,12 +57,12 @@ abstract class IntegrationSpec extends TestKitBaseClass with BitcoindService wit
maxFeeProportional = 0.03,
maxCltv = CltvExpiryDelta(Int.MaxValue),
maxRouteLength = ROUTE_MAX_LENGTH),
- heuristics = PaymentWeightRatios(
- baseFactor = 0,
- cltvDeltaFactor = 1,
- ageFactor = 0,
- capacityFactor = 0,
+ heuristics = HeuristicsConstants(
+ lockedFundsRisk = 0,
+ failureFees = RelayFees(0 msat, 0),
hopFees = RelayFees(0 msat, 0),
+ useLogProbability = false,
+ usePastRelaysData = false
),
mpp = MultiPartParams(15000000 msat, 6),
experimentName = "my-test-experiment",
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala
index 162f0f7..6ff332b 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala
@@ -43,7 +43,7 @@ import fr.acinq.eclair.payment.receive.MultiPartHandler.ReceiveStandardPayment
import fr.acinq.eclair.payment.relay.Relayer
import fr.acinq.eclair.payment.relay.Relayer.RelayFees
import fr.acinq.eclair.payment.send.PaymentInitiator.{SendPaymentToNode, SendTrampolinePayment}
-import fr.acinq.eclair.router.Graph.PaymentWeightRatios
+import fr.acinq.eclair.router.Graph.HeuristicsConstants
import fr.acinq.eclair.router.Router.{ChannelHop, GossipDecision, PublicChannel}
import fr.acinq.eclair.router.{Announcements, AnnouncementsBatchValidationSpec, Router}
import fr.acinq.eclair.wire.protocol.OfferTypes.{Offer, OfferPaths}
@@ -337,15 +337,15 @@ class PaymentIntegrationSpec extends IntegrationSpec {
val (sender, holdTimesRecorder) = (TestProbe(), TestProbe())
nodes("A").system.eventStream.subscribe(holdTimesRecorder.ref, classOf[Router.ReportedHoldTimes])
// first we retrieve a payment hash from C
- val amountMsat = 2000.msat
+ val amountMsat = 200000.msat
sender.send(nodes("C").paymentHandler, ReceiveStandardPayment(sender.ref, Some(amountMsat), Left("Change from coffee")))
val invoice = sender.expectMsgType[Bolt11Invoice]
// the payment is requesting to use a capacity-optimized route which will select node G even though it's a bit more expensive
- sender.send(nodes("A").paymentInitiator, SendPaymentToNode(sender.ref, amountMsat, invoice, Nil, maxAttempts = 1, routeParams = integrationTestRouteParams.copy(heuristics = PaymentWeightRatios(0, 0, 0, 1, RelayFees(0 msat, 0)))))
+ sender.send(nodes("A").paymentInitiator, SendPaymentToNode(sender.ref, amountMsat, invoice, Nil, maxAttempts = 1, routeParams = integrationTestRouteParams.copy(heuristics = HeuristicsConstants(0, RelayFees(10000000000L msat, 10000000000L), RelayFees(0 msat, 0), useLogProbability = false, usePastRelaysData = false))))
sender.expectMsgType[UUID]
val ps = sender.expectMsgType[PaymentSent]
- ps.parts.foreach(part => assert(part.route.getOrElse(Nil).exists(_.nodeId == nodes("G").nodeParams.nodeId)))
+ ps.parts.foreach(part => assert(part.route.get.exists(_.nodeId == nodes("G").nodeParams.nodeId)))
assert(holdTimesRecorder.expectMsgType[Router.ReportedHoldTimes].holdTimes.map(_.remoteNodeId) == Seq("B", "G", "C").map(nodes(_).nodeParams.nodeId))
}
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala
index 39a8d8a..66d3486 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala
@@ -32,7 +32,7 @@ import fr.acinq.eclair.payment.send.PaymentInitiator.SendPaymentConfig
import fr.acinq.eclair.payment.send.PaymentLifecycle.SendPaymentToRoute
import fr.acinq.eclair.payment.send._
import fr.acinq.eclair.router.BaseRouterSpec.{blindedRouteFromHops, channelHopFromUpdate}
-import fr.acinq.eclair.router.Graph.PaymentWeightRatios
+import fr.acinq.eclair.router.Graph.HeuristicsConstants
import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.router.{Announcements, RouteNotFound}
import fr.acinq.eclair.wire.protocol._
@@ -696,7 +696,7 @@ object MultiPartPaymentLifecycleSpec {
0.00,
6,
CltvExpiryDelta(1008)),
- PaymentWeightRatios(1, 0, 0, 0, RelayFees(0 msat, 0)),
+ HeuristicsConstants(0, RelayFees(0 msat, 0), RelayFees(0 msat, 0), useLogProbability = false, usePastRelaysData = false),
MultiPartParams(1000 msat, 5),
experimentName = "my-test-experiment",
experimentPercentage = 100
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala
index 7408708..58b63fd 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala
@@ -42,7 +42,7 @@ import fr.acinq.eclair.payment.send.{ClearRecipient, PaymentLifecycle, Recipient
import fr.acinq.eclair.reputation.{Reputation, ReputationRecorder}
import fr.acinq.eclair.router.Announcements.makeChannelUpdate
import fr.acinq.eclair.router.BaseRouterSpec.{blindedRouteFromHops, channelAnnouncement, channelHopFromUpdate}
-import fr.acinq.eclair.router.Graph.PaymentWeightRatios
+import fr.acinq.eclair.router.Graph.HeuristicsConstants
import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.router._
import fr.acinq.eclair.transactions.Scripts
@@ -301,7 +301,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
val routeParams = PathFindingConf(
randomize = false,
boundaries = SearchBoundaries(100 msat, 0.0, 20, CltvExpiryDelta(2016)),
- PaymentWeightRatios(1, 0, 0, 0, RelayFees(0 msat, 0)),
+ HeuristicsConstants(0, RelayFees(0 msat, 0), RelayFees(0 msat, 0), useLogProbability = false, usePastRelaysData = false),
MultiPartParams(10_000 msat, 5),
"my-test-experiment",
experimentPercentage = 100
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 2d346a5..5e15a56 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
@@ -21,7 +21,7 @@ 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.{DirectedGraph, GraphEdge}
-import fr.acinq.eclair.router.Graph.{HeuristicsConstants, MessagePathWeight, MessageWeightRatios, PaymentWeightRatios, dijkstraMessagePath, routeBlindingPaths, yenKshortestPaths}
+import fr.acinq.eclair.router.Graph.{HeuristicsConstants, MessagePathWeight, MessageWeightRatios, dijkstraMessagePath, routeBlindingPaths, yenKshortestPaths}
import fr.acinq.eclair.router.RouteCalculationSpec._
import fr.acinq.eclair.router.Router.ChannelDesc
import fr.acinq.eclair.wire.protocol.Color
@@ -288,7 +288,7 @@ class GraphSpec extends AnyFunSuite {
val paths = yenKshortestPaths(GraphWithBalanceEstimates(graph, 1 day), a, e, 90000000 msat,
Set.empty, Set.empty, Set.empty, 2,
- PaymentWeightRatios(1, 0, 0, 0, RelayFees(0 msat, 0)),
+ HeuristicsConstants(0, RelayFees(0 msat, 0), RelayFees(0 msat, 0), useLogProbability = false, usePastRelaysData = false),
BlockHeight(714930), _ => true, includeLocalChannelCost = true)
assert(paths.length == 2)
@@ -314,7 +314,7 @@ class GraphSpec extends AnyFunSuite {
val paths = yenKshortestPaths(GraphWithBalanceEstimates(graph, 1 day), a, e, 90000000 msat,
Set.empty, Set.empty, Set.empty, 2,
- PaymentWeightRatios(1, 0, 0, 0, RelayFees(0 msat, 0)),
+ HeuristicsConstants(0, RelayFees(0 msat, 0), RelayFees(0 msat, 0), useLogProbability = false, usePastRelaysData = false),
BlockHeight(714930), _ => true, includeLocalChannelCost = true)
// Even though paths to find is 2, we only find 1 because that is all the valid paths that there are.
@@ -347,7 +347,7 @@ class GraphSpec extends AnyFunSuite {
val paths = yenKshortestPaths(GraphWithBalanceEstimates(graph, 1 day), c, h, 10000000 msat,
Set.empty, Set.empty, Set.empty, 3,
- PaymentWeightRatios(1, 0, 0, 0, RelayFees(0 msat, 0)),
+ HeuristicsConstants(0, RelayFees(0 msat, 0), RelayFees(0 msat, 0), useLogProbability = false, usePastRelaysData = false),
BlockHeight(714930), _ => true, includeLocalChannelCost = true)
assert(paths.length == 3)
assert(paths(0).path == Seq(edgeCE, edgeEF, edgeFH))
@@ -388,7 +388,7 @@ class GraphSpec extends AnyFunSuite {
val paths = yenKshortestPaths(GraphWithBalanceEstimates(graph, 1 day), a, b, 10000000 msat,
Set.empty, Set.empty, Set.empty, 1,
- PaymentWeightRatios(1, 0, 0, 0, RelayFees(0 msat, 0)),
+ HeuristicsConstants(0, RelayFees(0 msat, 0), RelayFees(0 msat, 0), useLogProbability = false, usePastRelaysData = false),
BlockHeight(714930), _ => true, includeLocalChannelCost = true)
assert(paths.head.path == Seq(edgeAB))
}
@@ -524,13 +524,13 @@ class GraphSpec extends AnyFunSuite {
.addOrUpdateVertex(makeNodeAnnouncement(priv_h, "H", Color(0, 0, 0), Nil, Features(Features.RouteBlinding -> FeatureSupport.Optional)))
{
- val paths = routeBlindingPaths(GraphWithBalanceEstimates(graph, 1 day), a, h, 20_000_000 msat, Set.empty, Set.empty, pathsToFind = 3, PaymentWeightRatios(1, 0, 0, 0, RelayFees(0 msat, 0)), BlockHeight(793397), _ => true)
+ val paths = routeBlindingPaths(GraphWithBalanceEstimates(graph, 1 day), a, h, 20_000_000 msat, Set.empty, Set.empty, pathsToFind = 3, HeuristicsConstants(0, RelayFees(0 msat, 0), RelayFees(0 msat, 0), useLogProbability = false, usePastRelaysData = false), BlockHeight(793397), _ => true)
assert(paths.length == 2)
assert(paths(0).path.map(_.desc.a) == Seq(a, b))
assert(paths(1).path.map(_.desc.a) == Seq(a, e, f))
}
{
- val paths = routeBlindingPaths(GraphWithBalanceEstimates(graph, 1 day), c, h, 20_000_000 msat, Set.empty, Set.empty, pathsToFind = 3, PaymentWeightRatios(1, 0, 0, 0, RelayFees(0 msat, 0)), BlockHeight(793397), _ => true)
+ val paths = routeBlindingPaths(GraphWithBalanceEstimates(graph, 1 day), c, h, 20_000_000 msat, Set.empty, Set.empty, pathsToFind = 3, HeuristicsConstants(0, RelayFees(0 msat, 0), RelayFees(0 msat, 0), useLogProbability = false, usePastRelaysData = false), BlockHeight(793397), _ => true)
assert(paths.length == 1)
assert(paths(0).path.map(_.desc.a) == Seq(c, a, b))
}
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 c80c06b..470a090 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
@@ -18,13 +18,13 @@ package fr.acinq.eclair.router
import com.softwaremill.quicklens.ModifyPimp
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
-import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Satoshi, SatoshiLong, TxId}
+import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, Satoshi, SatoshiLong, TxId}
import fr.acinq.eclair.payment.relay.Relayer.RelayFees
import fr.acinq.eclair.router.Announcements.makeNodeAnnouncement
import fr.acinq.eclair.router.BaseRouterSpec.channelHopFromUpdate
import fr.acinq.eclair.router.Graph.GraphStructure.DirectedGraph.graphEdgeToHop
import fr.acinq.eclair.router.Graph.GraphStructure.{DirectedGraph, GraphEdge}
-import fr.acinq.eclair.router.Graph.{HeuristicsConstants, PaymentPathWeight, PaymentWeightRatios}
+import fr.acinq.eclair.router.Graph.{HeuristicsConstants, PaymentPathWeight}
import fr.acinq.eclair.router.RouteCalculation._
import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.transactions.Transactions
@@ -35,7 +35,6 @@ import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.{ParallelTestExecution, Tag}
import scodec.bits._
-import scala.collection.immutable.SortedMap
import scala.collection.mutable
import scala.concurrent.duration.DurationInt
import scala.util.{Failure, Random, Success}
@@ -814,48 +813,25 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution {
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)
- val Success(routeCltvOptimized :: Nil) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = PaymentWeightRatios(
- baseFactor = 0,
- cltvDeltaFactor = 1,
- ageFactor = 0,
- capacityFactor = 0,
+ val Success(routeCltvOptimized :: Nil) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = HeuristicsConstants(
+ lockedFundsRisk = 1,
+ failureFees = RelayFees(0 msat, 0),
hopFees = RelayFees(0 msat, 0),
+ useLogProbability = false,
+ usePastRelaysData = false,
)), currentBlockHeight = BlockHeight(400000))
assert(route2Nodes(routeCltvOptimized) == (a, e) :: (e, f) :: (f, d) :: Nil)
- val Success(routeCapacityOptimized :: Nil) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = PaymentWeightRatios(
- baseFactor = 0,
- cltvDeltaFactor = 0,
- ageFactor = 0,
- capacityFactor = 1,
+ val Success(routeCapacityOptimized :: Nil) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = HeuristicsConstants(
+ lockedFundsRisk = 0,
+ failureFees = RelayFees(1000 msat, 1000),
hopFees = RelayFees(0 msat, 0),
+ useLogProbability = false,
+ usePastRelaysData = false,
)), currentBlockHeight = BlockHeight(400000))
assert(route2Nodes(routeCapacityOptimized) == (a, e) :: (e, c) :: (c, d) :: Nil)
}
- test("prefer going through an older channel if fees and CLTV are the same") {
- val currentBlockHeight = BlockHeight(554000)
-
- 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 = PaymentWeightRatios(
- baseFactor = 0.01,
- ageFactor = 0.33,
- cltvDeltaFactor = 0.33,
- capacityFactor = 0.33,
- hopFees = RelayFees(0 msat, 0),
- )), currentBlockHeight = currentBlockHeight)
-
- assert(route2Nodes(routeScoreOptimized) == (a, b) :: (b, c) :: (c, d) :: Nil)
- }
-
test("prefer a route with a smaller total CLTV if fees and score are the same") {
val g = GraphWithBalanceEstimates(DirectedGraph(List(
makeEdge(1, a, b, feeBase = 1 msat, 0, minHtlc = 0 msat, maxHtlc = None, cltvDelta = CltvExpiryDelta(12)),
@@ -866,12 +842,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution {
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 = PaymentWeightRatios(
- baseFactor = 0.01,
- ageFactor = 0.33,
- cltvDeltaFactor = 0.33,
- capacityFactor = 0.33,
+ val Success(routeScoreOptimized :: Nil) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = HeuristicsConstants(
+ lockedFundsRisk = 1e-7,
+ failureFees = RelayFees(100 msat, 100),
hopFees = RelayFees(0 msat, 0),
+ useLogProbability = false,
+ usePastRelaysData = false,
)), currentBlockHeight = BlockHeight(400000))
assert(route2Nodes(routeScoreOptimized) == (a, b) :: (b, c) :: (c, d) :: Nil)
@@ -889,60 +865,17 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution {
makeEdge(6, f, d, feeBase = 100 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 = PaymentWeightRatios(
- baseFactor = 0.2,
- ageFactor = 0.4,
- cltvDeltaFactor = 0,
- capacityFactor = 0.4,
- hopFees = RelayFees(0 msat, 0),
+ val Success(routeScoreOptimized :: Nil) = findRoute(g, a, d, DEFAULT_AMOUNT_MSAT / 2, DEFAULT_MAX_FEE, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = HeuristicsConstants(
+ lockedFundsRisk = 0,
+ failureFees = RelayFees(100 msat, 100),
+ hopFees = RelayFees(500 msat, 200),
+ useLogProbability = false,
+ usePastRelaysData = false,
)), currentBlockHeight = BlockHeight(400000))
assert(route2Nodes(routeScoreOptimized) == (a, e) :: (e, f) :: (f, d) :: Nil)
}
- test("cost function is monotonic") {
- // This test have a channel (542280x2156x0) that according to heuristics is very convenient but actually useless to reach the target,
- // then if the cost function is not monotonic the path-finding breaks because the result path contains a loop.
- val updates = SortedMap(
- RealShortChannelId(BlockHeight(565643), 1216, 0) -> PublicChannel(
- ann = makeChannel(ShortChannelId.fromCoordinates("565643x1216x0").success.value.toLong, PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca")),
- fundingTxId = TxId(ByteVector32.Zeroes),
- capacity = DEFAULT_CAPACITY,
- update_1_opt = Some(ChannelUpdate(ByteVector64.Zeroes, Block.RegtestGenesisBlock.hash, ShortChannelId.fromCoordinates("565643x1216x0").success.value, 0 unixsec, ChannelUpdate.MessageFlags(dontForward = false), ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(14), htlcMinimumMsat = 1 msat, feeBaseMsat = 1000 msat, 10, 4_294_967_295L msat)),
- update_2_opt = Some(ChannelUpdate(ByteVector64.Zeroes, Block.RegtestGenesisBlock.hash, ShortChannelId.fromCoordinates("565643x1216x0").success.value, 0 unixsec, ChannelUpdate.MessageFlags(dontForward = false), ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), CltvExpiryDelta(144), htlcMinimumMsat = 0 msat, feeBaseMsat = 1000 msat, 100, 15_000_000_000L msat)),
- meta_opt = None
- ),
- RealShortChannelId(BlockHeight(542280), 2156, 0) -> PublicChannel(
- ann = makeChannel(ShortChannelId.fromCoordinates("542280x2156x0").success.value.toLong, PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"03cb7983dc247f9f81a0fa2dfa3ce1c255365f7279c8dd143e086ca333df10e278")),
- fundingTxId = TxId(ByteVector32.Zeroes),
- capacity = DEFAULT_CAPACITY,
- update_1_opt = Some(ChannelUpdate(ByteVector64.Zeroes, Block.RegtestGenesisBlock.hash, ShortChannelId.fromCoordinates("542280x2156x0").success.value, 0 unixsec, ChannelUpdate.MessageFlags(dontForward = false), ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(144), htlcMinimumMsat = 1000 msat, feeBaseMsat = 1000 msat, 100, 16_777_000_000L msat)),
- update_2_opt = Some(ChannelUpdate(ByteVector64.Zeroes, Block.RegtestGenesisBlock.hash, ShortChannelId.fromCoordinates("542280x2156x0").success.value, 0 unixsec, ChannelUpdate.MessageFlags(dontForward = false), ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), CltvExpiryDelta(144), htlcMinimumMsat = 1 msat, feeBaseMsat = 667 msat, 1, 16_777_000_000L msat)),
- meta_opt = None
- ),
- RealShortChannelId(BlockHeight(565779), 2711, 0) -> PublicChannel(
- ann = makeChannel(ShortChannelId.fromCoordinates("565779x2711x0").success.value.toLong, PublicKey(hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")),
- fundingTxId = TxId(ByteVector32.Zeroes),
- capacity = DEFAULT_CAPACITY,
- update_1_opt = Some(ChannelUpdate(ByteVector64.Zeroes, Block.RegtestGenesisBlock.hash, ShortChannelId.fromCoordinates("565779x2711x0").success.value, 0 unixsec, ChannelUpdate.MessageFlags(dontForward = false), ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(144), htlcMinimumMsat = 1 msat, feeBaseMsat = 1000 msat, 100, 230_000_000L msat)),
- update_2_opt = Some(ChannelUpdate(ByteVector64.Zeroes, Block.RegtestGenesisBlock.hash, ShortChannelId.fromCoordinates("565779x2711x0").success.value, 0 unixsec, ChannelUpdate.MessageFlags(dontForward = false), ChannelUpdate.ChannelFlags(isEnabled = false, isNode1 = false), CltvExpiryDelta(144), htlcMinimumMsat = 1 msat, feeBaseMsat = 1000 msat, 100, 230_000_000L msat)),
- meta_opt = None
- )
- )
-
- val g = GraphWithBalanceEstimates(DirectedGraph.makeGraph(updates, Seq.empty), 1 day)
- val params = DEFAULT_ROUTE_PARAMS
- .modify(_.boundaries.maxCltv).setTo(CltvExpiryDelta(1008))
- .modify(_.heuristics).setTo(PaymentWeightRatios(baseFactor = 0, cltvDeltaFactor = 0.15, ageFactor = 0.35, capacityFactor = 0.5, hopFees = RelayFees(0 msat, 0)))
- val thisNode = PublicKey(hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96")
- val targetNode = PublicKey(hex"024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca")
- val amount = 351000 msat
-
- val Success(route :: Nil) = findRoute(g, thisNode, targetNode, amount, DEFAULT_MAX_FEE, 1, Set.empty, Set.empty, Set.empty, params, currentBlockHeight = BlockHeight(567634)) // simulate mainnet block for heuristic
- assert(route.hops.length == 2)
- assert(route.hops.last.nextNodeId == targetNode)
- }
-
test("validate path fees") {
val ab = makeEdge(1L, a, b, feeBase = 100 msat, 10000, minHtlc = 150 msat, maxHtlc = Some(300 msat), capacity = 1 sat, balance_opt = Some(260 msat))
val bc = makeEdge(10L, b, c, feeBase = 5 msat, 10000, minHtlc = 100 msat, maxHtlc = Some(400 msat), capacity = 1 sat)
@@ -1755,13 +1688,13 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution {
assert(route2Ids(route) == 0 :: 2 :: 5 :: 6 :: 7 :: 4 :: Nil)
}
{ // small base hop cost
- val Success(routes) = findRoute(g, start, b, DEFAULT_AMOUNT_MSAT, 100000000 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = PaymentWeightRatios(1, 0, 0, 0, RelayFees(100 msat, 0))), currentBlockHeight = BlockHeight(400000))
+ val Success(routes) = findRoute(g, start, b, DEFAULT_AMOUNT_MSAT, 100000000 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = HeuristicsConstants(0, RelayFees(0 msat, 0), RelayFees(100 msat, 0), useLogProbability = false, usePastRelaysData = false)), currentBlockHeight = BlockHeight(400000))
assert(routes.distinct.length == 1)
val route :: Nil = routes
assert(route2Ids(route) == 0 :: 2 :: 3 :: 4 :: Nil)
}
{ // large proportional hop cost
- val Success(routes) = findRoute(g, start, b, DEFAULT_AMOUNT_MSAT, 100000000 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = PaymentWeightRatios(1, 0, 0, 0, RelayFees(0 msat, 200))), currentBlockHeight = BlockHeight(400000))
+ val Success(routes) = findRoute(g, start, b, DEFAULT_AMOUNT_MSAT, 100000000 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = HeuristicsConstants(0, RelayFees(0 msat, 0), RelayFees(0 msat, 200), useLogProbability = false, usePastRelaysData = false)), currentBlockHeight = BlockHeight(400000))
assert(routes.distinct.length == 1)
val route :: Nil = routes
assert(route2Ids(route) == 0 :: 1 :: Nil)
@@ -1870,12 +1803,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution {
makeEdge(recentChannelId, a, c, 1000 msat, 100),
)), 1 day)
- val wr = PaymentWeightRatios(
- baseFactor = 0,
- cltvDeltaFactor = 0,
- ageFactor = 0.5,
- capacityFactor = 0.5,
+ val wr = HeuristicsConstants(
+ lockedFundsRisk = 0,
+ failureFees = RelayFees(100 msat, 100),
hopFees = RelayFees(500 msat, 200),
+ useLogProbability = false,
+ usePastRelaysData = false,
)
val Success(routes) = findRoute(g, a, c, DEFAULT_AMOUNT_MSAT, 100_000_000 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = wr), currentBlockHeight = BlockHeight(400000))
assert(routes.distinct.length == 1)
@@ -1907,12 +1840,12 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution {
makeEdge(4L, c, d, 100 msat, 100, minHtlc = 1000 msat, capacity = 100000000 sat),
)), 1 day)
- val wr = PaymentWeightRatios(
- baseFactor = 0,
- cltvDeltaFactor = 0,
- ageFactor = 0,
- capacityFactor = 1,
- hopFees = RelayFees(500 msat, 200),
+ val wr = HeuristicsConstants(
+ lockedFundsRisk = 0,
+ failureFees = RelayFees(1000 msat, 1000),
+ hopFees = RelayFees(0 msat, 0),
+ useLogProbability = false,
+ usePastRelaysData = false,
)
val Success(routes) = findRoute(g, a, d, 50000 msat, 100000000 msat, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(heuristics = wr, includeLocalChannelCost = true), currentBlockHeight = BlockHeight(400000))
val route :: Nil = routes
@@ -1961,7 +1894,7 @@ object RouteCalculationSpec {
val DEFAULT_EXPIRY = CltvExpiry(TestConstants.defaultBlockHeight)
val DEFAULT_CAPACITY = 100_000 sat
- val NO_WEIGHT_RATIOS: PaymentWeightRatios = PaymentWeightRatios(1, 0, 0, 0, RelayFees(0 msat, 0))
+ val NO_WEIGHT_RATIOS: HeuristicsConstants = HeuristicsConstants(0, RelayFees(0 msat, 0), RelayFees(0 msat, 0), useLogProbability = false, usePastRelaysData = false)
val DEFAULT_ROUTE_PARAMS = PathFindingConf(
randomize = false,
boundaries = SearchBoundaries(21000 msat, 0.03, 6, CltvExpiryDelta(2016)),
@@ -2012,7 +1945,7 @@ object RouteCalculationSpec {
def routes2Ids(routes: Seq[Route]): Set[Seq[Long]] = routes.map(route2Ids).toSet
- def route2Edges(route: Route): Seq[GraphEdge] = route.hops.map(hop => GraphEdge(ChannelDesc(hop.shortChannelId, hop.nodeId, hop.nextNodeId), hop.params, 0 sat, None))
+ def route2Edges(route: Route): Seq[GraphEdge] = route.hops.map(hop => GraphEdge(ChannelDesc(hop.shortChannelId, hop.nodeId, hop.nextNodeId), hop.params, 1000000 sat, None))
def route2Nodes(route: Route): Seq[(PublicKey, PublicKey)] = route.hops.map(hop => (hop.nodeId, hop.nextNodeId))
Why this scored 20/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.