diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md index 56ac5d4413..ede157a96b 100644 --- a/docs/release-notes/eclair-vnext.md +++ b/docs/release-notes/eclair-vnext.md @@ -55,6 +55,7 @@ This feature leaks a bit of information about the balance when the channel is al ### API changes - `bumpforceclose` can be used to make a force-close confirm faster, by spending the anchor output (#2743) +- `nodes` allows filtering nodes that offer liquidity ads (#2550) ### Miscellaneous improvements and bug fixes diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 82fe9de1d5..647b3f6d34 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -533,6 +533,14 @@ eclair { enabled = true // enable automatic purges of expired invoices from the database interval = 24 hours // interval between expired invoice purges } + + // Liquidity Ads allow remote nodes to pay us to provide them with inbound liquidity. + liquidity-ads { + enabled = false // set this field to true if you want to sell your unused on-chain liquidity + fee-base-satoshis = 1000 // flat fee that we will receive every time we accept a lease request + fee-basis-points = 500 // 5% of the liquidity we will provide + max-duration-blocks = 4032 // ~1 month + } } akka { 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..bac93cc3a2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -86,7 +86,8 @@ case class NodeParams(nodeKeyManager: NodeKeyManager, blockchainWatchdogThreshold: Int, blockchainWatchdogSources: Seq[String], onionMessageConfig: OnionMessageConfig, - purgeInvoicesInterval: Option[FiniteDuration]) { + purgeInvoicesInterval: Option[FiniteDuration], + liquidityAdsConfig_opt: Option[LiquidityAds.Config]) { val privateKey: Crypto.PrivateKey = nodeKeyManager.nodeKey.privateKey val nodeId: PublicKey = nodeKeyManager.nodeId @@ -97,6 +98,8 @@ case class NodeParams(nodeKeyManager: NodeKeyManager, val pluginOpenChannelInterceptor: Option[InterceptOpenChannelPlugin] = pluginParams.collectFirst { case p: InterceptOpenChannelPlugin => p } + val liquidityRates_opt: Option[LiquidityAds.LeaseRates] = liquidityAdsConfig_opt.map(_.leaseRates(relayParams.defaultFees(announceChannel = true))) + def currentBlockHeight: BlockHeight = BlockHeight(blockHeight.get) def currentFeerates: FeeratesPerKw = feerates.get() @@ -604,7 +607,16 @@ object NodeParams extends Logging { timeout = FiniteDuration(config.getDuration("onion-messages.reply-timeout").getSeconds, TimeUnit.SECONDS), maxAttempts = config.getInt("onion-messages.max-attempts"), ), - purgeInvoicesInterval = purgeInvoicesInterval + purgeInvoicesInterval = purgeInvoicesInterval, + liquidityAdsConfig_opt = if (config.getBoolean("liquidity-ads.enabled")) { + Some(LiquidityAds.Config( + feeBase = Satoshi(config.getInt("liquidity-ads.fee-base-satoshis")), + feeProportional = config.getInt("liquidity-ads.fee-basis-points"), + maxLeaseDuration = config.getInt("liquidity-ads.max-duration-blocks"), + )) + } else { + None + }, ) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala index fe11bf8c6d..ccef7b0d89 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala @@ -68,7 +68,7 @@ object Announcements { ) } - def makeNodeAnnouncement(nodeSecret: PrivateKey, alias: String, color: Color, nodeAddresses: List[NodeAddress], features: Features[NodeFeature], timestamp: TimestampSecond = TimestampSecond.now()): NodeAnnouncement = { + def makeNodeAnnouncement(nodeSecret: PrivateKey, alias: String, color: Color, nodeAddresses: List[NodeAddress], features: Features[NodeFeature], liquidityRates_opt: Option[LiquidityAds.LeaseRates], timestamp: TimestampSecond = TimestampSecond.now()): NodeAnnouncement = { require(alias.length <= 32) // sort addresses by ascending address descriptor type; do not reorder addresses within the same descriptor type val sortedAddresses = nodeAddresses.map { @@ -78,7 +78,10 @@ object Announcements { case address@(_: Tor3) => (4, address) case address@(_: DnsHostname) => (5, address) }.sortBy(_._1).map(_._2) - val witness = nodeAnnouncementWitnessEncode(timestamp, nodeSecret.publicKey, color, alias, features.unscoped(), sortedAddresses, TlvStream.empty) + val tlvs: Set[NodeAnnouncementTlv] = Set( + liquidityRates_opt.map(NodeAnnouncementTlv.LiquidityAdsTlv), + ).flatten + val witness = nodeAnnouncementWitnessEncode(timestamp, nodeSecret.publicKey, color, alias, features.unscoped(), sortedAddresses, TlvStream(tlvs)) val sig = Crypto.sign(witness, nodeSecret) NodeAnnouncement( signature = sig, @@ -87,7 +90,8 @@ object Announcements { rgbColor = color, alias = alias, features = features.unscoped(), - addresses = sortedAddresses + addresses = sortedAddresses, + tlvStream = TlvStream(tlvs) ) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index c3111c8833..3396eb5854 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -99,7 +99,7 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm // on restart we update our node announcement // note that if we don't currently have public channels, this will be ignored - val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses, nodeParams.features.nodeAnnouncementFeatures()) + val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses, nodeParams.features.nodeAnnouncementFeatures(), nodeParams.liquidityRates_opt) self ! nodeAnn log.info("initialization completed, ready to process messages") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index 25feb8c0e6..a391b92154 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -209,7 +209,7 @@ object Validation { // in case this was our first local channel, we make a node announcement if (!d.nodes.contains(nodeParams.nodeId) && isRelatedTo(ann, nodeParams.nodeId)) { log.info("first local channel validated, announcing local node") - val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses, nodeParams.features.nodeAnnouncementFeatures()) + val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses, nodeParams.features.nodeAnnouncementFeatures(), nodeParams.liquidityRates_opt) handleNodeAnnouncement(d1, nodeParams.db.network, Set(LocalGossip), nodeAnn) } else d1 } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LiquidityAds.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LiquidityAds.scala index 3f14bd5fdb..83ca06b564 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LiquidityAds.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LiquidityAds.scala @@ -23,7 +23,7 @@ import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.wire.protocol.CommonCodecs.{blockHeight, millisatoshi32, publicKey, satoshi32} import fr.acinq.eclair.wire.protocol.TlvCodecs.tmillisatoshi32 -import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, MilliSatoshi, ToMilliSatoshiConversion} +import fr.acinq.eclair.{BlockHeight, MilliSatoshi, ToMilliSatoshiConversion} import scodec.Codec import scodec.bits.ByteVector import scodec.codecs._ @@ -41,8 +41,13 @@ import java.nio.charset.StandardCharsets */ object LiquidityAds { - /** Liquidity leases are valid for a fixed duration, after which they must be renewed. */ - val LeaseDuration = CltvExpiryDelta(1008) // 1 week + case class Config(feeBase: Satoshi, feeProportional: Int, maxLeaseDuration: Int) { + def leaseRates(relayFees: RelayFees): LeaseRates = { + // We make the remote node pay for one p2wpkh input and one p2wpkh output. + // If we need more inputs, we will pay the fees for those additional inputs ourselves. + LeaseRates(Transactions.claimP2WPKHOutputWeight, feeProportional, (relayFees.feeProportionalMillionths / 100).toInt, feeBase, relayFees.feeBase) + } + } /** * Liquidity is leased using the following rates: @@ -70,12 +75,6 @@ object LiquidityAds { } } - object LeaseRates { - def apply(fundingWeight: Int, leaseFeeBase: Satoshi, leaseFeeProportional: Int, relayFees: RelayFees): LeaseRates = { - LeaseRates(fundingWeight, leaseFeeProportional, (relayFees.feeProportionalMillionths / 100).toInt, leaseFeeBase, relayFees.feeBase) - } - } - val leaseRatesCodec: Codec[LeaseRates] = ( ("funding_weight" | uint16) :: ("lease_fee_basis" | uint16) :: 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 19ad419cf3..9003409a6e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -30,7 +30,7 @@ import fr.acinq.eclair.payment.relay.Relayer.{AsyncPaymentsParams, RelayFees, Re import fr.acinq.eclair.router.Graph.{MessagePath, WeightRatios} import fr.acinq.eclair.router.PathFindingExperimentConf import fr.acinq.eclair.router.Router.{MessageRouteParams, MultiPartParams, PathFindingConf, RouterConf, SearchBoundaries} -import fr.acinq.eclair.wire.protocol.{Color, EncodingType, NodeAddress, OnionRoutingPacket} +import fr.acinq.eclair.wire.protocol.{Color, EncodingType, LiquidityAds, NodeAddress, OnionRoutingPacket} import org.scalatest.Tag import scodec.bits.{ByteVector, HexStringSyntax} @@ -51,6 +51,7 @@ object TestConstants { val feeratePerKw: FeeratePerKw = FeeratePerKw(10_000 sat) val anchorOutputsFeeratePerKw: FeeratePerKw = FeeratePerKw(2_500 sat) val emptyOnionPacket: OnionRoutingPacket = OnionRoutingPacket(0, ByteVector.fill(33)(0), ByteVector.fill(1300)(0), ByteVector32.Zeroes) + val defaultLiquidityRates: LiquidityAds.LeaseRates = LiquidityAds.LeaseRates(500, 100, 10, 100 sat, 200 msat) case object TestFeature extends Feature with InitFeature with NodeFeature { val rfcName = "test_feature" @@ -224,7 +225,8 @@ object TestConstants { timeout = 200 millis, maxAttempts = 2, ), - purgeInvoicesInterval = None + purgeInvoicesInterval = None, + liquidityAdsConfig_opt = None, ) def channelParams: LocalParams = OpenChannelInterceptor.makeChannelParams( @@ -389,7 +391,8 @@ object TestConstants { timeout = 100 millis, maxAttempts = 2, ), - purgeInvoicesInterval = None + purgeInvoicesInterval = None, + liquidityAdsConfig_opt = None, ) def channelParams: LocalParams = OpenChannelInterceptor.makeChannelParams( diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala index 5bc11f6096..39abdcf10c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala @@ -29,7 +29,7 @@ import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.router.Router.PublicChannel import fr.acinq.eclair.wire.protocol.LightningMessageCodecs.{channelAnnouncementCodec, channelUpdateCodec, nodeAnnouncementCodec} import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, RealShortChannelId, ShortChannelId, TestDatabases, randomBytes32, randomKey} +import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, RealShortChannelId, ShortChannelId, TestConstants, TestDatabases, randomBytes32, randomKey} import org.scalatest.funsuite.AnyFunSuite import scodec.bits.HexStringSyntax @@ -56,11 +56,11 @@ class NetworkDbSpec extends AnyFunSuite { forAllDbs { dbs => val db = dbs.network - val node_1 = Announcements.makeNodeAnnouncement(randomKey(), "node-alice", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features.empty) - val node_2 = Announcements.makeNodeAnnouncement(randomKey(), "node-bob", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features(VariableLengthOnion -> Optional)) - val node_3 = Announcements.makeNodeAnnouncement(randomKey(), "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features(VariableLengthOnion -> Optional)) - val node_4 = Announcements.makeNodeAnnouncement(randomKey(), "node-eve", Color(100.toByte, 200.toByte, 300.toByte), Tor3("of7husrflx7sforh3fw6yqlpwstee3wg5imvvmkp4bz6rbjxtg5nljad", 42000) :: Nil, Features.empty) - val node_5 = Announcements.makeNodeAnnouncement(randomKey(), "node-frank", Color(100.toByte, 200.toByte, 300.toByte), DnsHostname("eclair.invalid", 42000) :: Nil, Features.empty) + val node_1 = Announcements.makeNodeAnnouncement(randomKey(), "node-alice", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features.empty, None) + val node_2 = Announcements.makeNodeAnnouncement(randomKey(), "node-bob", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features(VariableLengthOnion -> Optional), Some(TestConstants.defaultLiquidityRates)) + val node_3 = Announcements.makeNodeAnnouncement(randomKey(), "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features(VariableLengthOnion -> Optional), None) + val node_4 = Announcements.makeNodeAnnouncement(randomKey(), "node-eve", Color(100.toByte, 200.toByte, 300.toByte), Tor3("of7husrflx7sforh3fw6yqlpwstee3wg5imvvmkp4bz6rbjxtg5nljad", 42000) :: Nil, Features.empty, None) + val node_5 = Announcements.makeNodeAnnouncement(randomKey(), "node-frank", Color(100.toByte, 200.toByte, 300.toByte), DnsHostname("eclair.invalid", 42000) :: Nil, Features.empty, None) assert(db.listNodes().toSet == Set.empty) db.addNode(node_1) @@ -401,7 +401,7 @@ object NetworkDbSpec { update_2_data_opt: Option[Array[Byte]]) val nodeTestCases: Seq[NodeTestCase] = for (_ <- 0 until 10) yield { - val node = Announcements.makeNodeAnnouncement(randomKey(), "node-alice", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features.empty) + val node = Announcements.makeNodeAnnouncement(randomKey(), "node-alice", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features.empty, None) val data = nodeAnnouncementCodec.encode(node).require.toByteArray NodeTestCase( nodeId = node.nodeId, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/PgUtilsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/PgUtilsSpec.scala index 3ce41af8f6..e9d009d00d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/PgUtilsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/PgUtilsSpec.scala @@ -170,9 +170,9 @@ class PgUtilsSpec extends TestKitBaseClass with AnyFunSuiteLike with Eventually val db = Databases.postgres(baseConfig, UUID.randomUUID(), datadir, None, LockFailureHandler.logAndThrow) db.channels.addOrUpdateChannel(ChannelCodecsSpec.normal) db.channels.updateChannelMeta(ChannelCodecsSpec.normal.channelId, ChannelEvent.EventType.Created) - db.network.addNode(Announcements.makeNodeAnnouncement(randomKey(), "node-A", Color(50, 99, -80), Nil, Features.empty, TimestampSecond.now() - 45.days)) - db.network.addNode(Announcements.makeNodeAnnouncement(randomKey(), "node-B", Color(50, 99, -80), Nil, Features.empty, TimestampSecond.now() - 3.days)) - db.network.addNode(Announcements.makeNodeAnnouncement(randomKey(), "node-C", Color(50, 99, -80), Nil, Features.empty, TimestampSecond.now() - 7.minutes)) + db.network.addNode(Announcements.makeNodeAnnouncement(randomKey(), "node-A", Color(50, 99, -80), Nil, Features.empty, None, TimestampSecond.now() - 45.days)) + db.network.addNode(Announcements.makeNodeAnnouncement(randomKey(), "node-B", Color(50, 99, -80), Nil, Features.empty, None, TimestampSecond.now() - 3.days)) + db.network.addNode(Announcements.makeNodeAnnouncement(randomKey(), "node-C", Color(50, 99, -80), Nil, Features.empty, None, TimestampSecond.now() - 7.minutes)) db.audit.add(ChannelPaymentRelayed(421 msat, 400 msat, randomBytes32(), randomBytes32(), randomBytes32(), TimestampMilli.now() - 5.seconds, TimestampMilli.now() - 3.seconds)) db.dataSource.close() } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala index f355c645b7..805750734a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala @@ -16,15 +16,14 @@ package fr.acinq.eclair.router -import fr.acinq.bitcoin.scalacompat.Block import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} +import fr.acinq.bitcoin.scalacompat.{Block, SatoshiLong} import fr.acinq.eclair.TestConstants.Alice -import fr.acinq.eclair.RealShortChannelId -import fr.acinq.eclair._ +import fr.acinq.eclair.{RealShortChannelId, _} import fr.acinq.eclair.router.Announcements._ import fr.acinq.eclair.wire.protocol.ChannelUpdate.ChannelFlags import fr.acinq.eclair.wire.protocol.LightningMessageCodecs.nodeAnnouncementCodec -import fr.acinq.eclair.wire.protocol.NodeAddress +import fr.acinq.eclair.wire.protocol.{LiquidityAds, NodeAddress, TlvStream} import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ @@ -51,7 +50,7 @@ class AnnouncementsSpec extends AnyFunSuite { val bitcoin_b_sig = Announcements.signChannelAnnouncement(witness, bitcoin_b) val ann = makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(42), node_a.publicKey, node_b.publicKey, bitcoin_a.publicKey, bitcoin_b.publicKey, node_a_sig, node_b_sig, bitcoin_a_sig, bitcoin_b_sig) assert(checkSigs(ann)) - assert(checkSigs(ann.copy(nodeId1 = randomKey().publicKey)) == false) + assert(!checkSigs(ann.copy(nodeId1 = randomKey().publicKey))) } test("create valid signed node announcement") { @@ -65,7 +64,7 @@ class AnnouncementsSpec extends AnyFunSuite { Features.BasicMultiPartPayment -> FeatureSupport.Optional, Features.PaymentMetadata -> FeatureSupport.Optional, ) - val ann = makeNodeAnnouncement(Alice.nodeParams.privateKey, Alice.nodeParams.alias, Alice.nodeParams.color, Alice.nodeParams.publicAddresses, features.nodeAnnouncementFeatures()) + val ann = makeNodeAnnouncement(Alice.nodeParams.privateKey, Alice.nodeParams.alias, Alice.nodeParams.color, Alice.nodeParams.publicAddresses, features.nodeAnnouncementFeatures(), Some(TestConstants.defaultLiquidityRates)) // Features should be filtered to only include node_announcement related features. assert(ann.features == Features( Features.DataLossProtect -> FeatureSupport.Optional, @@ -76,7 +75,8 @@ class AnnouncementsSpec extends AnyFunSuite { Features.BasicMultiPartPayment -> FeatureSupport.Optional, )) assert(checkSig(ann)) - assert(checkSig(ann.copy(timestamp = 153 unixsec)) == false) + assert(!checkSig(ann.copy(timestamp = 153 unixsec))) + assert(!checkSig(ann.copy(tlvStream = TlvStream.empty))) } test("sort node announcement addresses") { @@ -86,7 +86,7 @@ class AnnouncementsSpec extends AnyFunSuite { NodeAddress.fromParts("2620:1ec:c11:0:0:0:0:200", 9735).get, NodeAddress.fromParts("140.82.121.4", 9735).get, ) - val ann = makeNodeAnnouncement(Alice.nodeParams.privateKey, Alice.nodeParams.alias, Alice.nodeParams.color, addresses, Alice.nodeParams.features.nodeAnnouncementFeatures()) + val ann = makeNodeAnnouncement(Alice.nodeParams.privateKey, Alice.nodeParams.alias, Alice.nodeParams.color, addresses, Alice.nodeParams.features.nodeAnnouncementFeatures(), None) assert(checkSig(ann)) assert(ann.addresses == List( NodeAddress.fromParts("140.82.121.4", 9735).get, @@ -111,7 +111,7 @@ class AnnouncementsSpec extends AnyFunSuite { NodeAddress.fromParts("acinq.co", 9735).get, NodeAddress.fromParts("acinq.fr", 9735).get, // ignore more than one DNS hostnames ) - val ann = makeNodeAnnouncement(Alice.nodeParams.privateKey, Alice.nodeParams.alias, Alice.nodeParams.color, addresses, Alice.nodeParams.features.nodeAnnouncementFeatures()) + val ann = makeNodeAnnouncement(Alice.nodeParams.privateKey, Alice.nodeParams.alias, Alice.nodeParams.color, addresses, Alice.nodeParams.features.nodeAnnouncementFeatures(), None) assert(checkSig(ann)) assert(ann.validAddresses === List( NodeAddress.fromParts("140.82.121.5", 9735).get, @@ -131,7 +131,7 @@ class AnnouncementsSpec extends AnyFunSuite { test("create valid signed channel update announcement") { val ann = makeChannelUpdate(Block.RegtestGenesisBlock.hash, Alice.nodeParams.privateKey, randomKey().publicKey, ShortChannelId(45561L), Alice.nodeParams.channelConf.expiryDelta, Alice.nodeParams.channelConf.htlcMinimum, Alice.nodeParams.relayParams.publicChannelFees.feeBase, Alice.nodeParams.relayParams.publicChannelFees.feeProportionalMillionths, 500000000 msat) assert(checkSig(ann, Alice.nodeParams.nodeId)) - assert(checkSig(ann, randomKey().publicKey) == false) + assert(!checkSig(ann, randomKey().publicKey)) } test("check flags") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala index e1f716739b..e5d39e4d45 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala @@ -69,13 +69,13 @@ abstract class BaseRouterSpec extends TestKitBaseClass with FixtureAnyFunSuiteLi val (funding_a, funding_b, funding_c, funding_d, funding_e, funding_f, funding_g, funding_h) = (priv_funding_a.publicKey, priv_funding_b.publicKey, priv_funding_c.publicKey, priv_funding_d.publicKey, priv_funding_e.publicKey, priv_funding_f.publicKey, priv_funding_g.publicKey, priv_funding_h.publicKey) // in the tests we are 'a', we don't define a node_a, it will be generated automatically when the router validates the first channel - val node_b = makeNodeAnnouncement(priv_b, "node-B", Color(50, 99, -80), Nil, Features.empty) - val node_c = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), Nil, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures()) - val node_d = makeNodeAnnouncement(priv_d, "node-D", Color(-120, -20, 60), Nil, Features.empty) - val node_e = makeNodeAnnouncement(priv_e, "node-E", Color(-50, 0, 10), Nil, Features.empty) - val node_f = makeNodeAnnouncement(priv_f, "node-F", Color(30, 10, -50), Nil, Features.empty) - val node_g = makeNodeAnnouncement(priv_g, "node-G", Color(30, 10, -50), Nil, Features.empty) - val node_h = makeNodeAnnouncement(priv_h, "node-H", Color(30, 10, -50), Nil, Features.empty) + val node_b = makeNodeAnnouncement(priv_b, "node-B", Color(50, 99, -80), Nil, Features.empty, None) + val node_c = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), Nil, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), None) + val node_d = makeNodeAnnouncement(priv_d, "node-D", Color(-120, -20, 60), Nil, Features.empty, None) + val node_e = makeNodeAnnouncement(priv_e, "node-E", Color(-50, 0, 10), Nil, Features.empty, None) + val node_f = makeNodeAnnouncement(priv_f, "node-F", Color(30, 10, -50), Nil, Features.empty, None) + val node_g = makeNodeAnnouncement(priv_g, "node-G", Color(30, 10, -50), Nil, Features.empty, None) + val node_h = makeNodeAnnouncement(priv_h, "node-H", Color(30, 10, -50), Nil, Features.empty, None) val scid_ab = RealShortChannelId(BlockHeight(420000), 1, 0) val scid_bc = RealShortChannelId(BlockHeight(420000), 2, 0) 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..b967ed80f2 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,34 +17,31 @@ 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 class GraphSpec extends AnyFunSuite { val (priv_a, priv_b, priv_c, priv_d, priv_e, priv_f, priv_g, priv_h) = (randomKey(), randomKey(), randomKey(), randomKey(), randomKey(), randomKey(), randomKey(), randomKey()) val (a, b, c, d, e, f, g, h) = (priv_a.publicKey, priv_b.publicKey, priv_c.publicKey, priv_d.publicKey, priv_e.publicKey, priv_f.publicKey, priv_g.publicKey, priv_h.publicKey) val (annA, annB, annC, annD, annE, annF, annG, annH) = ( - makeNodeAnnouncement(priv_a, "A", Color(0, 0, 0), Nil, Features.empty), - makeNodeAnnouncement(priv_b, "B", Color(0, 0, 0), Nil, Features.empty), - makeNodeAnnouncement(priv_c, "C", Color(0, 0, 0), Nil, Features.empty), - makeNodeAnnouncement(priv_d, "D", Color(0, 0, 0), Nil, Features.empty), - makeNodeAnnouncement(priv_e, "E", Color(0, 0, 0), Nil, Features.empty), - makeNodeAnnouncement(priv_f, "F", Color(0, 0, 0), Nil, Features.empty), - makeNodeAnnouncement(priv_g, "G", Color(0, 0, 0), Nil, Features.empty), - makeNodeAnnouncement(priv_h, "H", Color(0, 0, 0), Nil, Features.empty), + makeNodeAnnouncement(priv_a, "A", Color(0, 0, 0), Nil, Features.empty, None), + makeNodeAnnouncement(priv_b, "B", Color(0, 0, 0), Nil, Features.empty, None), + makeNodeAnnouncement(priv_c, "C", Color(0, 0, 0), Nil, Features.empty, None), + makeNodeAnnouncement(priv_d, "D", Color(0, 0, 0), Nil, Features.empty, None), + makeNodeAnnouncement(priv_e, "E", Color(0, 0, 0), Nil, Features.empty, None), + makeNodeAnnouncement(priv_f, "F", Color(0, 0, 0), Nil, Features.empty, None), + makeNodeAnnouncement(priv_g, "G", Color(0, 0, 0), Nil, Features.empty, None), + makeNodeAnnouncement(priv_h, "H", Color(0, 0, 0), Nil, Features.empty, None), ) // +---- D -------+ @@ -357,26 +354,26 @@ class GraphSpec extends AnyFunSuite { } test("RoutingHeuristics.normalize") { - // value inside the range - assert(Graph.RoutingHeuristics.normalize(value = 10, min = 0, max = 100) === (10.0 / 100.0) +- 0.001) - assert(Graph.RoutingHeuristics.normalize(value = 20, min = 10, max = 200) === (10.0 / 190.0) +- 0.001) - assert(Graph.RoutingHeuristics.normalize(value = -11, min = -100, max = -10) === (89.0 / 90.0) +- 0.001) - - // value on the bounds - assert(Graph.RoutingHeuristics.normalize(value = 0, min = 0, max = 100) > 0) - assert(Graph.RoutingHeuristics.normalize(value = 10, min = 10, max = 200) > 0) - assert(Graph.RoutingHeuristics.normalize(value = -100, min = -100, max = -10) > 0) - assert(Graph.RoutingHeuristics.normalize(value = 9.1, min = 10, max = 200) > 0) - assert(Graph.RoutingHeuristics.normalize(value = 100, min = 0, max = 100) < 1) - assert(Graph.RoutingHeuristics.normalize(value = 200, min = 10, max = 200) < 1) - - // value outside the range - assert(Graph.RoutingHeuristics.normalize(value = 105.2, min = 0, max = 100) < 1) - - // Should throw exception if min > max - assertThrows[IllegalArgumentException]( - Graph.RoutingHeuristics.normalize(value = 9, min = 10, max = 1) - ) + // value inside the range + assert(Graph.RoutingHeuristics.normalize(value = 10, min = 0, max = 100) === (10.0 / 100.0) +- 0.001) + assert(Graph.RoutingHeuristics.normalize(value = 20, min = 10, max = 200) === (10.0 / 190.0) +- 0.001) + assert(Graph.RoutingHeuristics.normalize(value = -11, min = -100, max = -10) === (89.0 / 90.0) +- 0.001) + + // value on the bounds + assert(Graph.RoutingHeuristics.normalize(value = 0, min = 0, max = 100) > 0) + assert(Graph.RoutingHeuristics.normalize(value = 10, min = 10, max = 200) > 0) + assert(Graph.RoutingHeuristics.normalize(value = -100, min = -100, max = -10) > 0) + assert(Graph.RoutingHeuristics.normalize(value = 9.1, min = 10, max = 200) > 0) + assert(Graph.RoutingHeuristics.normalize(value = 100, min = 0, max = 100) < 1) + assert(Graph.RoutingHeuristics.normalize(value = 200, min = 10, max = 200) < 1) + + // value outside the range + assert(Graph.RoutingHeuristics.normalize(value = 105.2, min = 0, max = 100) < 1) + + // Should throw exception if min > max + assertThrows[IllegalArgumentException]( + Graph.RoutingHeuristics.normalize(value = 9, min = 10, max = 1) + ) } test("local channel is preferred") { @@ -410,11 +407,11 @@ class GraphSpec extends AnyFunSuite { makeEdge(4L, e, a, 7 msat, 7, capacity = 1000 sat, minHtlc = 700 msat, maxHtlc = Some(800 msat)), makeEdge(5L, d, e, 8 msat, 8, capacity = 1000 sat, minHtlc = 800 msat, maxHtlc = Some(900 msat)), makeEdge(5L, e, d, 9 msat, 9, capacity = 1000 sat, minHtlc = 900 msat, maxHtlc = Some(1000 msat)), - )).addOrUpdateVertex(makeNodeAnnouncement(priv_a, "A", Color(0, 0, 0), Nil, Features(Features.OnionMessages -> FeatureSupport.Optional))) - .addOrUpdateVertex(makeNodeAnnouncement(priv_b, "B", Color(0, 0, 0), Nil, Features(Features.OnionMessages -> FeatureSupport.Optional))) - .addOrUpdateVertex(makeNodeAnnouncement(priv_c, "C", Color(0, 0, 0), Nil, Features(Features.OnionMessages -> FeatureSupport.Optional))) - .addOrUpdateVertex(makeNodeAnnouncement(priv_d, "D", Color(0, 0, 0), Nil, Features(Features.OnionMessages -> FeatureSupport.Optional))) - .addOrUpdateVertex(makeNodeAnnouncement(priv_e, "E", Color(0, 0, 0), Nil, Features(Features.OnionMessages -> FeatureSupport.Optional))) + )).addOrUpdateVertex(makeNodeAnnouncement(priv_a, "A", Color(0, 0, 0), Nil, Features(Features.OnionMessages -> FeatureSupport.Optional), None)) + .addOrUpdateVertex(makeNodeAnnouncement(priv_b, "B", Color(0, 0, 0), Nil, Features(Features.OnionMessages -> FeatureSupport.Optional), None)) + .addOrUpdateVertex(makeNodeAnnouncement(priv_c, "C", Color(0, 0, 0), Nil, Features(Features.OnionMessages -> FeatureSupport.Optional), None)) + .addOrUpdateVertex(makeNodeAnnouncement(priv_d, "D", Color(0, 0, 0), Nil, Features(Features.OnionMessages -> FeatureSupport.Optional), None)) + .addOrUpdateVertex(makeNodeAnnouncement(priv_e, "E", Color(0, 0, 0), Nil, Features(Features.OnionMessages -> FeatureSupport.Optional), None)) { // All nodes can relay messages, same weight for each channel. @@ -427,8 +424,8 @@ class GraphSpec extends AnyFunSuite { // Source and target don't relay messages but they can still emit and receive. val boundaries = (w: MessagePath.RichWeight) => w.length <= 8 val wr = MessagePath.WeightRatios(1.0, 0.0, 0.0) - val g = graph.addOrUpdateVertex(makeNodeAnnouncement(priv_a, "A", Color(0, 0, 0), Nil, Features.empty)) - .addOrUpdateVertex(makeNodeAnnouncement(priv_d, "D", Color(0, 0, 0), Nil, Features.empty)) + val g = graph.addOrUpdateVertex(makeNodeAnnouncement(priv_a, "A", Color(0, 0, 0), Nil, Features.empty, None)) + .addOrUpdateVertex(makeNodeAnnouncement(priv_d, "D", Color(0, 0, 0), Nil, Features.empty, None)) val Some(path) = MessagePath.dijkstraMessagePath(g, a, d, Set.empty, boundaries, BlockHeight(793397), wr) assert(path.map(_.shortChannelId.toLong) == Seq(4, 5)) } @@ -436,7 +433,7 @@ class GraphSpec extends AnyFunSuite { // E doesn't relay messages. val boundaries = (w: MessagePath.RichWeight) => w.length <= 8 val wr = MessagePath.WeightRatios(1.0, 0.0, 0.0) - val g = graph.addOrUpdateVertex(makeNodeAnnouncement(priv_e, "E", Color(0, 0, 0), Nil, Features.empty)) + val g = graph.addOrUpdateVertex(makeNodeAnnouncement(priv_e, "E", Color(0, 0, 0), Nil, Features.empty, None)) val Some(path) = MessagePath.dijkstraMessagePath(g, a, d, Set.empty, boundaries, BlockHeight(793397), wr) assert(path.map(_.shortChannelId.toLong) == Seq(1, 2, 3)) } 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..f59b73f650 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 @@ -318,10 +318,10 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("route not found (source OR target node not connected)") { val priv_a = randomKey() val a = priv_a.publicKey - val annA = makeNodeAnnouncement(priv_a, "A", Color(0, 0, 0), Nil, Features.empty) + val annA = makeNodeAnnouncement(priv_a, "A", Color(0, 0, 0), Nil, Features.empty, None) val priv_e = randomKey() val e = priv_e.publicKey - val annE = makeNodeAnnouncement(priv_e, "E", Color(0, 0, 0), Nil, Features.empty) + val annE = makeNodeAnnouncement(priv_e, "E", Color(0, 0, 0), Nil, Features.empty, None) val g = DirectedGraph(List( makeEdge(2L, b, c, 0 msat, 0), diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala index a8c82aa9be..b337443878 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala @@ -59,7 +59,7 @@ class RouterSpec extends BaseRouterSpec { { // continue to rebroadcast node updates with deprecated Torv2 addresses val torv2Address = List(NodeAddress.fromParts("hsmithsxurybd7uh.onion", 9735).get) - val node_c_torv2 = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), torv2Address, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), timestamp = TimestampSecond.now() + 1) + val node_c_torv2 = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), torv2Address, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), None, timestamp = TimestampSecond.now() + 1) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, node_c_torv2)) peerConnection.expectMsg(TransportHandler.ReadAck(node_c_torv2)) peerConnection.expectMsg(GossipDecision.Accepted(node_c_torv2)) @@ -71,7 +71,7 @@ class RouterSpec extends BaseRouterSpec { { // rebroadcast node updates with a single DNS hostname addresses val hostname = List(NodeAddress.fromParts("acinq.co", 9735).get) - val node_c_hostname = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), hostname, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), timestamp = TimestampSecond.now() + 10) + val node_c_hostname = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), hostname, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), None, timestamp = TimestampSecond.now() + 10) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, node_c_hostname)) peerConnection.expectMsg(TransportHandler.ReadAck(node_c_hostname)) peerConnection.expectMsg(GossipDecision.Accepted(node_c_hostname)) @@ -83,7 +83,7 @@ class RouterSpec extends BaseRouterSpec { { // do NOT rebroadcast node updates with more than one DNS hostname addresses val multiHostnames = List(NodeAddress.fromParts("acinq.co", 9735).get, NodeAddress.fromParts("acinq.fr", 9735).get) - val node_c_noForward = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), multiHostnames, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), timestamp = TimestampSecond.now() + 20) + val node_c_noForward = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), multiHostnames, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), None, timestamp = TimestampSecond.now() + 20) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, node_c_noForward)) peerConnection.expectMsg(TransportHandler.ReadAck(node_c_noForward)) peerConnection.expectMsg(GossipDecision.Accepted(node_c_noForward)) @@ -104,7 +104,7 @@ class RouterSpec extends BaseRouterSpec { // valid channel announcement, no stashing val chan_ac = channelAnnouncement(RealShortChannelId(BlockHeight(420000), 5, 0), priv_a, priv_c, priv_funding_a, priv_funding_c) val update_ac = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, chan_ac.shortChannelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, htlcMaximum) - val node_c = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), Nil, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), timestamp = TimestampSecond.now() + 1) + val node_c = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), Nil, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), None, timestamp = TimestampSecond.now() + 1) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_ac)) peerConnection.expectNoMessage(100 millis) // we don't immediately acknowledge the announcement (back pressure) assert(watcher.expectMsgType[ValidateRequest].ann == chan_ac) @@ -133,7 +133,7 @@ class RouterSpec extends BaseRouterSpec { val priv_funding_u = randomKey() val chan_uc = channelAnnouncement(RealShortChannelId(BlockHeight(420000), 100, 0), priv_u, priv_c, priv_funding_u, priv_funding_c) val update_uc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_u, c, chan_uc.shortChannelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, htlcMaximum) - val node_u = makeNodeAnnouncement(priv_u, "node-U", Color(-120, -20, 60), Nil, Features.empty) + val node_u = makeNodeAnnouncement(priv_u, "node-U", Color(-120, -20, 60), Nil, Features.empty, None) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_uc)) peerConnection.expectNoMessage(200 millis) // we don't immediately acknowledge the announcement (back pressure) assert(watcher.expectMsgType[ValidateRequest].ann == chan_uc) @@ -229,7 +229,7 @@ class RouterSpec extends BaseRouterSpec { // unknown channel val priv_y = randomKey() val update_ay = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_y.publicKey, ShortChannelId(4646464), CltvExpiryDelta(7), 0 msat, 766000 msat, 10, htlcMaximum) - val node_y = makeNodeAnnouncement(priv_y, "node-Y", Color(123, 100, -40), Nil, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures()) + val node_y = makeNodeAnnouncement(priv_y, "node-Y", Color(123, 100, -40), Nil, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), None) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, update_ay)) peerConnection.expectMsg(TransportHandler.ReadAck(update_ay)) peerConnection.expectMsg(GossipDecision.NoRelatedChannel(update_ay)) @@ -246,7 +246,7 @@ class RouterSpec extends BaseRouterSpec { val priv_funding_y = randomKey() // a-y will have an invalid script val chan_ay = channelAnnouncement(RealShortChannelId(42002), priv_a, priv_y, priv_funding_a, priv_funding_y) val update_ay = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_y.publicKey, chan_ay.shortChannelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, htlcMaximum) - val node_y = makeNodeAnnouncement(priv_y, "node-Y", Color(123, 100, -40), Nil, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures()) + val node_y = makeNodeAnnouncement(priv_y, "node-Y", Color(123, 100, -40), Nil, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), None) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_ay)) assert(watcher.expectMsgType[ValidateRequest].ann == chan_ay) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, update_ay)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala index 44945a534a..9af28da1d4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala @@ -353,8 +353,8 @@ object RoutingSyncSpec { val channelAnn_12 = channelAnnouncement(shortChannelId, priv1, priv2, priv_funding1, priv_funding2) val channelUpdate_12 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv1, priv2.publicKey, shortChannelId, cltvExpiryDelta = CltvExpiryDelta(7), 0 msat, feeBaseMsat = 766000 msat, feeProportionalMillionths = 10, 500000000L msat, timestamp = timestamp) val channelUpdate_21 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv2, priv1.publicKey, shortChannelId, cltvExpiryDelta = CltvExpiryDelta(7), 0 msat, feeBaseMsat = 766000 msat, feeProportionalMillionths = 10, 500000000L msat, timestamp = timestamp) - val nodeAnnouncement_1 = makeNodeAnnouncement(priv1, "a", Color(0, 0, 0), List(), TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures()) - val nodeAnnouncement_2 = makeNodeAnnouncement(priv2, "b", Color(0, 0, 0), List(), Features.empty) + val nodeAnnouncement_1 = makeNodeAnnouncement(priv1, "a", Color(0, 0, 0), List(), TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), None) + val nodeAnnouncement_2 = makeNodeAnnouncement(priv2, "b", Color(0, 0, 0), List(), Features.empty, None) val publicChannel = PublicChannel(channelAnn_12, ByteVector32.Zeroes, Satoshi(0), Some(channelUpdate_12), Some(channelUpdate_21), None) (publicChannel, nodeAnnouncement_1, nodeAnnouncement_2) } @@ -370,7 +370,7 @@ object RoutingSyncSpec { def makeFakeNodeAnnouncement(pub2priv: mutable.Map[PublicKey, PrivateKey])(nodeId: PublicKey): NodeAnnouncement = { val priv = pub2priv(nodeId) - makeNodeAnnouncement(priv, "", Color(0, 0, 0), List(), Features.empty) + makeNodeAnnouncement(priv, "", Color(0, 0, 0), List(), Features.empty, None) } } diff --git a/eclair-front/src/main/scala/fr/acinq/eclair/router/FrontRouter.scala b/eclair-front/src/main/scala/fr/acinq/eclair/router/FrontRouter.scala index 676a2eef66..ad78b5ddc1 100644 --- a/eclair-front/src/main/scala/fr/acinq/eclair/router/FrontRouter.scala +++ b/eclair-front/src/main/scala/fr/acinq/eclair/router/FrontRouter.scala @@ -23,12 +23,11 @@ import akka.event.LoggingAdapter import fr.acinq.bitcoin.scalacompat.ByteVector32 import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.eclair.Logs.LogCategory -import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.io.Peer.PeerRoutingMessage import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{FSMDiagnosticActorLogging, Logs, ShortChannelId, getSimpleClassName} +import fr.acinq.eclair.{FSMDiagnosticActorLogging, Logs, RealShortChannelId, getSimpleClassName} import kamon.Kamon import kamon.metric.Counter diff --git a/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala b/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala index 0501dfcf67..4dc5def906 100644 --- a/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala +++ b/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala @@ -333,12 +333,12 @@ object FrontRouterSpec { val (priv_funding_a, priv_funding_b, priv_funding_c, priv_funding_d, priv_funding_e, priv_funding_f) = (randomKey(), randomKey(), randomKey(), randomKey(), randomKey(), randomKey()) val (funding_a, funding_b, funding_c, funding_d, funding_e, funding_f) = (priv_funding_a.publicKey, priv_funding_b.publicKey, priv_funding_c.publicKey, priv_funding_d.publicKey, priv_funding_e.publicKey, priv_funding_f.publicKey) - val ann_a = makeNodeAnnouncement(priv_a, "node-A", Color(15, 10, -70), Nil, Features(Features.VariableLengthOnion -> FeatureSupport.Optional)) - val ann_b = makeNodeAnnouncement(priv_b, "node-B", Color(50, 99, -80), Nil, Features.empty) - val ann_c = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), Nil, Features(Features.VariableLengthOnion -> FeatureSupport.Optional)) - val ann_d = makeNodeAnnouncement(priv_d, "node-D", Color(-120, -20, 60), Nil, Features.empty) - val ann_e = makeNodeAnnouncement(priv_e, "node-E", Color(-50, 0, 10), Nil, Features.empty) - val ann_f = makeNodeAnnouncement(priv_f, "node-F", Color(30, 10, -50), Nil, Features.empty) + val ann_a = makeNodeAnnouncement(priv_a, "node-A", Color(15, 10, -70), Nil, Features(Features.VariableLengthOnion -> FeatureSupport.Optional), None) + val ann_b = makeNodeAnnouncement(priv_b, "node-B", Color(50, 99, -80), Nil, Features.empty, None) + val ann_c = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), Nil, Features(Features.VariableLengthOnion -> FeatureSupport.Optional), None) + val ann_d = makeNodeAnnouncement(priv_d, "node-D", Color(-120, -20, 60), Nil, Features.empty, None) + val ann_e = makeNodeAnnouncement(priv_e, "node-E", Color(-50, 0, 10), Nil, Features.empty, None) + val ann_f = makeNodeAnnouncement(priv_f, "node-F", Color(30, 10, -50), Nil, Features.empty, None) val channelId_ab = RealShortChannelId(BlockHeight(420000), 1, 0) val channelId_bc = RealShortChannelId(BlockHeight(420000), 2, 0) diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/PathFinding.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/PathFinding.scala index 8e685bc9c1..e7a6491238 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/PathFinding.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/PathFinding.scala @@ -63,8 +63,13 @@ trait PathFinding { } val nodes: Route = postRequest("nodes") { implicit t => - formFields(nodeIdsFormParam.?) { nodeIds_opt => - complete(eclairApi.nodes(nodeIds_opt.map(_.toSet))) + formFields(nodeIdsFormParam.?, "liquidityProvider".as[Boolean].?) { (nodeIds_opt, liquidityProviders_opt) => + complete(eclairApi.nodes(nodeIds_opt.map(_.toSet)).map(_.filter { n => + liquidityProviders_opt match { + case Some(true) => n.liquidityRates_opt.nonEmpty + case _ => true + } + })) } }