Skip to content

Commit

Permalink
Improve findRoute ignored channels behavior (#2523)
Browse files Browse the repository at this point in the history
We use router data to resolve both private and public channels, even
when scid-alias is used.

When a channel cannot be found, we simply skip it since adding it to the
ignore list wouldn't have any impact.

Fixes #2346 and #2426
  • Loading branch information
t-bast authored Dec 29, 2022
1 parent c2eb357 commit 7c50528
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 10 deletions.
13 changes: 5 additions & 8 deletions eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ import scodec.{Attempt, DecodeResult, codecs}

import java.nio.charset.StandardCharsets
import java.util.UUID
import scala.collection.immutable.SortedMap
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.{Failure, Success}
Expand Down Expand Up @@ -538,14 +537,12 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
Future.successful(Set.empty)
} else {
for {
channelsMap <- (appKit.router ? GetChannelsMap).mapTo[SortedMap[ShortChannelId, PublicChannel]]
routerData <- (appKit.router ? GetRouterData).mapTo[Router.Data]
} yield {
shortChannelIds.flatMap { id =>
val c = channelsMap.getOrElse(id, throw new IllegalArgumentException(s"unknown channel: $id"))
Set(
ChannelDesc(c.ann.shortChannelId, c.ann.nodeId1, c.ann.nodeId2),
ChannelDesc(c.ann.shortChannelId, c.ann.nodeId2, c.ann.nodeId1))
}
shortChannelIds.flatMap(scid => routerData.resolve(scid) match {
case Some(c) => Set(ChannelDesc(scid, c.nodeId1, c.nodeId2), ChannelDesc(scid, c.nodeId2, c.nodeId1))
case None => Set.empty
})
}
}
}
Expand Down
53 changes: 51 additions & 2 deletions eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ import fr.acinq.eclair.payment.receive.PaymentHandler
import fr.acinq.eclair.payment.relay.Relayer.{GetOutgoingChannels, RelayFees}
import fr.acinq.eclair.payment.send.PaymentInitiator._
import fr.acinq.eclair.payment.{Bolt11Invoice, Invoice, PaymentFailed}
import fr.acinq.eclair.router.Graph.GraphStructure.DirectedGraph
import fr.acinq.eclair.router.RouteCalculationSpec.makeUpdateShort
import fr.acinq.eclair.router.Router.{PredefinedNodeRoute, PublicChannel}
import fr.acinq.eclair.router.{Announcements, Router}
import fr.acinq.eclair.router.Router.{PredefinedNodeRoute, PrivateChannel, PublicChannel, RouteRequest}
import fr.acinq.eclair.router.{Announcements, GraphWithBalanceEstimates, Router}
import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec
import fr.acinq.eclair.wire.protocol.{ChannelUpdate, Color, NodeAnnouncement}
import org.mockito.scalatest.IdiomaticMockito
Expand Down Expand Up @@ -316,6 +317,54 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I
paymentInitiator.expectMsg(SendPaymentToRoute(1200 msat, pr, route, Some("42"), Some(parentId), Some(TrampolineAttempt(secret, 100 msat, CltvExpiryDelta(144)))))
}

test("find routes") { f =>
import f._

val eclair = new EclairImpl(kit)
val (a, b, c) = (randomKey().publicKey, randomKey().publicKey, randomKey().publicKey)
val channel1 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(1), a, b, a, b, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes)
val channel2 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(2), b, c, b, c, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes)
val publicChannels = SortedMap(
channel1.shortChannelId -> PublicChannel(channel1, ByteVector32.Zeroes, 100_000 sat, None, None, None),
channel2.shortChannelId -> PublicChannel(channel2, ByteVector32.Zeroes, 150_000 sat, None, None, None),
)
val (channelId3, shortIds3) = (randomBytes32(), ShortIds(RealScidStatus.Unknown, Alias(13), None))
val (channelId4, shortIds4) = (randomBytes32(), ShortIds(RealScidStatus.Final(RealShortChannelId(4)), Alias(14), None))
val privateChannels = Map(
channelId3 -> PrivateChannel(channelId3, shortIds3, a, b, None, None, Router.ChannelMeta(25_000 msat, 50_000 msat)),
channelId4 -> PrivateChannel(channelId4, shortIds4, a, c, None, None, Router.ChannelMeta(75_000 msat, 10_000 msat)),
)
val scidMapping = Map(
shortIds3.localAlias.toLong -> channelId3,
shortIds4.localAlias.toLong -> channelId4,
shortIds4.real.toOption.get.toLong -> channelId4,
)
val g = GraphWithBalanceEstimates(DirectedGraph(Nil), 1 hour)
val routerData = Router.Data(Map.empty, publicChannels, SortedMap.empty, Router.Stash(Map.empty, Map.empty), Router.Rebroadcast(Map.empty, Map.empty, Map.empty), Map.empty, privateChannels, scidMapping, Map.empty, g, Map.empty)

eclair.findRoute(c, 250_000 msat, None)
val routeRequest1 = router.expectMsgType[RouteRequest]
assert(routeRequest1.target == c)
assert(routeRequest1.ignore == Router.Ignore.empty)

val unknownNodeId = randomKey().publicKey
val unknownScid = Alias(42)
eclair.findRoute(c, 250_000 msat, None, ignoreNodeIds = Seq(b, unknownNodeId), ignoreShortChannelIds = Seq(channel1.shortChannelId, shortIds3.localAlias, shortIds4.real.toOption.get, unknownScid))
router.expectMsg(Router.GetRouterData)
router.reply(routerData)
val routeRequest2 = router.expectMsgType[RouteRequest]
assert(routeRequest2.target == c)
assert(routeRequest2.ignore.nodes == Set(b, unknownNodeId))
assert(routeRequest2.ignore.channels == Set(
Router.ChannelDesc(channel1.shortChannelId, a, b),
Router.ChannelDesc(channel1.shortChannelId, b, a),
Router.ChannelDesc(shortIds3.localAlias, a, b),
Router.ChannelDesc(shortIds3.localAlias, b, a),
Router.ChannelDesc(shortIds4.real.toOption.get, a, c),
Router.ChannelDesc(shortIds4.real.toOption.get, c, a),
))
}

test("call sendWithPreimage, which generates a random preimage, to perform a KeySend payment") { f =>
import f._

Expand Down

0 comments on commit 7c50528

Please sign in to comment.