Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support DNS hostnames in node announcements #2234

Merged
merged 16 commits into from
Aug 16, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/release-notes/eclair-vnext.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ Expired incoming invoices that are unpaid will be searched for and purged from t
* `eclair.purge-expired-invoices.enabled = true
* `eclair.purge-expired-invoices.interval = 24 hours`

#### Public IP addresses can be DNS host names

You can now specify a DNS host name as one of your `server.public-ips` addresses (see PR [#911](https://github.com/lightning/bolts/pull/911)). Note: you can not specify more than one DNS host name.

## Verifying signatures

You will need `gpg` and our release signing key 7A73FE77DE2C4027. Note that you can get it:
Expand Down
1 change: 1 addition & 0 deletions eclair-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ eclair {
use-for-ipv6 = true
use-for-tor = true
use-for-watchdogs = true
use-for-dnshostnames = true
randomize-credentials = false // this allows tor stream isolation
}

Expand Down
19 changes: 18 additions & 1 deletion eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ import fr.acinq.eclair.io.MessageRelay.{NoRelay, RelayAll, RelayChannelsOnly, Re
import fr.acinq.eclair.io.PeerConnection
import fr.acinq.eclair.message.OnionMessages.OnionMessageConfig
import fr.acinq.eclair.payment.relay.Relayer.{RelayFees, RelayParams}
import fr.acinq.eclair.router.Announcements.AddressException
import fr.acinq.eclair.router.Graph.{HeuristicsConstants, WeightRatios}
import fr.acinq.eclair.router.PathFindingExperimentConf
import fr.acinq.eclair.router.Router.{MultiPartParams, PathFindingConf, RouterConf, SearchBoundaries}
import fr.acinq.eclair.tor.Socks5ProxyParams
import fr.acinq.eclair.wire.protocol.{Color, EncodingType, NodeAddress}
import fr.acinq.eclair.wire.protocol._
import grizzled.slf4j.Logging
import scodec.bits.ByteVector

Expand Down Expand Up @@ -177,6 +178,7 @@ object NodeParams extends Logging {
useForIPv6 = config.getBoolean("socks5.use-for-ipv6"),
useForTor = config.getBoolean("socks5.use-for-tor"),
useForWatchdogs = config.getBoolean("socks5.use-for-watchdogs"),
useForDnsHostnames = config.getBoolean("socks5.use-for-dnshostnames"),
))
} else {
None
Expand Down Expand Up @@ -297,6 +299,19 @@ object NodeParams extends Logging {
require(features.hasFeature(Features.ChannelType), s"${Features.ChannelType.rfcName} must be enabled")
}

def validateAddresses(addresses: List[NodeAddress]): Unit = {
val addressesError = if (addresses.count(_.isInstanceOf[DnsHostname]) > 1) {
Some(AddressException(s"Invalid server.public-ip addresses: can not have more than one DNS host name."))
} else {
addresses.collectFirst {
case address if address.isInstanceOf[Tor2] => AddressException(s"invalid server.public-ip address `$address`: Tor v2 is deprecated.")
case address if address.port == 0 && !address.isInstanceOf[Tor3] => AddressException(s"invalid server.public-ip address `$address`: A non-Tor address can not use port 0.")
}
}

require(addressesError.isEmpty, addressesError.map(_.message))
}

val pluginMessageParams = pluginParams.collect { case p: CustomFeaturePlugin => p }
val features = Features.fromConfiguration(config.getConfig("features"))
validateFeatures(features)
Expand Down Expand Up @@ -328,6 +343,8 @@ object NodeParams extends Logging {
.toList
.map(ip => NodeAddress.fromParts(ip, config.getInt("server.port")).get) ++ publicTorAddress_opt

validateAddresses(addresses)

val feeTargets = FeeTargets(
fundingBlockTarget = config.getInt("on-chain-fees.target-blocks.funding"),
commitmentBlockTarget = config.getInt("on-chain-fees.target-blocks.commitment"),
Expand Down
3 changes: 2 additions & 1 deletion eclair-core/src/main/scala/fr/acinq/eclair/io/Client.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ class Client(keyPair: KeyPair, socks5ProxyParams_opt: Option[Socks5ProxyParams],

def receive: Receive = {
case Symbol("connect") =>
// note that there is no resolution here, it's either plain ip addresses, or unresolved tor hostnames
// note that only DNS host names are resolved here; plain ip addresses and tor hostnames are not resolved
val remoteAddress = remoteNodeAddress match {
case addr: IPv4 => new InetSocketAddress(addr.ipv4, addr.port)
case addr: IPv6 => new InetSocketAddress(addr.ipv6, addr.port)
case addr: Tor2 => InetSocketAddress.createUnresolved(addr.host, addr.port)
case addr: Tor3 => InetSocketAddress.createUnresolved(addr.host, addr.port)
case addr: DnsHostname => new InetSocketAddress(addr.host, addr.port)
}
val (peerOrProxyAddress, proxyParams_opt) = socks5ProxyParams_opt.map(proxyParams => (proxyParams, Socks5ProxyParams.proxyAddress(remoteNodeAddress, proxyParams))) match {
case Some((proxyParams, Some(proxyAddress))) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ object ReconnectionTask {
}

def getPeerAddressFromDb(nodeParams: NodeParams, remoteNodeId: PublicKey): Option[NodeAddress] = {
val nodeAddresses = nodeParams.db.peers.getPeer(remoteNodeId).toSeq ++ nodeParams.db.network.getNode(remoteNodeId).toSeq.flatMap(_.addresses)
val nodeAddresses = nodeParams.db.peers.getPeer(remoteNodeId).toSeq ++ nodeParams.db.network.getNode(remoteNodeId).toList.flatMap(_.validAddresses)
selectNodeAddress(nodeParams, nodeAddresses)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,13 @@ object Announcements {

def makeNodeAnnouncement(nodeSecret: PrivateKey, alias: String, color: Color, nodeAddresses: List[NodeAddress], features: Features[NodeFeature], 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 {
case address@(_: IPv4) => (1, address)
case address@(_: IPv6) => (2, address)
case address@(_: Tor2) => (3, address)
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 sig = Crypto.sign(witness, nodeSecret)
Expand All @@ -89,6 +91,8 @@ object Announcements {
)
}

case class AddressException(message: String) extends IllegalArgumentException(message)

/**
* BOLT 7:
* The creating node MUST set node-id-1 and node-id-2 to the public keys of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,12 @@ object Validation {
log.debug("received node announcement from {}", ctx.sender())
None
}
val rebroadcastNode = if (n.shouldRebroadcast) {
Some(n -> origins)
} else {
log.debug("will not rebroadcast {}", n)
None
}
if (d.stash.nodes.contains(n)) {
log.debug("ignoring {} (already stashed)", n)
val origins1 = d.stash.nodes(n) ++ origins
Expand All @@ -228,13 +234,13 @@ object Validation {
remoteOrigins.foreach(sendDecision(_, GossipDecision.Accepted(n)))
ctx.system.eventStream.publish(NodeUpdated(n))
db.updateNode(n)
d.copy(nodes = d.nodes + (n.nodeId -> n), rebroadcast = d.rebroadcast.copy(nodes = d.rebroadcast.nodes + (n -> origins)))
d.copy(nodes = d.nodes + (n.nodeId -> n), rebroadcast = d.rebroadcast.copy(nodes = d.rebroadcast.nodes ++ rebroadcastNode))
} else if (d.channels.values.exists(c => isRelatedTo(c.ann, n.nodeId))) {
log.debug("added node nodeId={}", n.nodeId)
remoteOrigins.foreach(sendDecision(_, GossipDecision.Accepted(n)))
ctx.system.eventStream.publish(NodesDiscovered(n :: Nil))
db.addNode(n)
d.copy(nodes = d.nodes + (n.nodeId -> n), rebroadcast = d.rebroadcast.copy(nodes = d.rebroadcast.nodes + (n -> origins)))
d.copy(nodes = d.nodes + (n.nodeId -> n), rebroadcast = d.rebroadcast.copy(nodes = d.rebroadcast.nodes ++ rebroadcastNode))
} else if (d.awaiting.keys.exists(c => isRelatedTo(c, n.nodeId))) {
log.debug("stashing {}", n)
d.copy(stash = d.stash.copy(nodes = d.stash.nodes + (n -> origins)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ object Socks5Connection {
def portToByteString(port: Int): ByteString = ByteString((port & 0x0000ff00) >> 8, port & 0x000000ff)
}

case class Socks5ProxyParams(address: InetSocketAddress, credentials_opt: Option[Credentials], randomizeCredentials: Boolean, useForIPv4: Boolean, useForIPv6: Boolean, useForTor: Boolean, useForWatchdogs: Boolean)
case class Socks5ProxyParams(address: InetSocketAddress, credentials_opt: Option[Credentials], randomizeCredentials: Boolean, useForIPv4: Boolean, useForIPv6: Boolean, useForTor: Boolean, useForWatchdogs: Boolean, useForDnsHostnames: Boolean)

object Socks5ProxyParams {

Expand All @@ -237,6 +237,7 @@ object Socks5ProxyParams {
case _: IPv6 if proxyParams.useForIPv6 => Some(proxyParams.address)
case _: Tor2 if proxyParams.useForTor => Some(proxyParams.address)
case _: Tor3 if proxyParams.useForTor => Some(proxyParams.address)
case _: DnsHostname if proxyParams.useForDnsHostnames => Some(proxyParams.address)
case _ => None
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ object CommonCodecs {
.typecase(2, (ipv6address :: uint16).as[IPv6])
.typecase(3, (base32(10) :: uint16).as[Tor2])
.typecase(4, (base32(35) :: uint16).as[Tor3])
.typecase( 5, (variableSizeBytes(uint8, ascii) :: uint16).as[DnsHostname])
t-bast marked this conversation as resolved.
Show resolved Hide resolved

// this one is a bit different from most other codecs: the first 'len' element is *not* the number of items
// in the list but rather the number of bytes of the encoded list. The rationale is once we've read this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.channel.{ChannelFlags, ChannelType}
import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Feature, Features, InitFeature, MilliSatoshi, ShortChannelId, TimestampSecond, UInt64}
import scodec.bits.ByteVector
import sun.net.util.IPAddressUtil.{isIPv4LiteralAddress, isIPv6LiteralAddress}
t-bast marked this conversation as resolved.
Show resolved Hide resolved

import java.net.{Inet4Address, Inet6Address, InetAddress}
import java.nio.charset.StandardCharsets
Expand Down Expand Up @@ -312,16 +313,20 @@ object NodeAddress {
/**
* Creates a NodeAddress from a host and port.
*
* Note that non-onion hosts will be resolved.
* Note that only IP v4 and v6 hosts will be resolved, onion and DNS hosts names will not be resolved.
*
* We don't attempt to resolve onion addresses (it will be done by the tor proxy), so we just recognize them based on
* the .onion TLD and rely on their length to separate v2/v3.
*
* Host names that are not Tor, IPv4 or IPv6 are assumed to be a DNS name and are not immediately resolved.
*
*/
def fromParts(host: String, port: Int): Try[NodeAddress] = Try {
host match {
case _ if host.endsWith(".onion") && host.length == 22 => Tor2(host.dropRight(6), port)
case _ if host.endsWith(".onion") && host.length == 62 => Tor3(host.dropRight(6), port)
case _ => IPAddress(InetAddress.getByName(host), port)
case _ if isIPv4LiteralAddress(host) || isIPv6LiteralAddress(host) => IPAddress(InetAddress.getByName(host), port)
case _ => DnsHostname(host, port)
}
}

Expand All @@ -348,6 +353,7 @@ case class IPv4(ipv4: Inet4Address, port: Int) extends IPAddress { override def
case class IPv6(ipv6: Inet6Address, port: Int) extends IPAddress { override def host: String = InetAddresses.toUriString(ipv6) }
case class Tor2(tor2: String, port: Int) extends OnionAddress { override def host: String = tor2 + ".onion" }
case class Tor3(tor3: String, port: Int) extends OnionAddress { override def host: String = tor3 + ".onion" }
case class DnsHostname(dnsHostname: String, port: Int) extends IPAddress {override def host: String = dnsHostname}
// @formatter:on

case class NodeAnnouncement(signature: ByteVector64,
Expand All @@ -357,7 +363,20 @@ case class NodeAnnouncement(signature: ByteVector64,
rgbColor: Color,
alias: String,
addresses: List[NodeAddress],
tlvStream: TlvStream[NodeAnnouncementTlv] = TlvStream.empty) extends RoutingMessage with AnnouncementMessage with HasTimestamp
tlvStream: TlvStream[NodeAnnouncementTlv] = TlvStream.empty) extends RoutingMessage with AnnouncementMessage with HasTimestamp {

val validAddresses: List[NodeAddress] = {
// if port is equal to 0, SHOULD ignore ipv6_addr OR ipv4_addr OR hostname; SHOULD ignore Tor v2 onion services.
val validAddresses = addresses.filter(address => address.port != 0 || address.isInstanceOf[Tor3]).filterNot( address => address.isInstanceOf[Tor2])
t-bast marked this conversation as resolved.
Show resolved Hide resolved
// if more than one type 5 address is announced, SHOULD ignore the additional data.
validAddresses.filter(!_.isInstanceOf[DnsHostname]) ++ validAddresses.find(_.isInstanceOf[DnsHostname])
}

val shouldRebroadcast: Boolean = {
// if more than one type 5 address is announced, MUST not forward the node_announcement.
addresses.count(address => address.isInstanceOf[DnsHostname]) <= 1
}
}

case class ChannelUpdate(signature: ByteVector64,
chainHash: ByteVector32,
Expand Down
24 changes: 24 additions & 0 deletions eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,28 @@ class StartupSpec extends AnyFunSuite {
assert(nodeParamsAttempt2.isSuccess)
}

test("NodeParams should fail when server.public-ips addresses or server.port are invalid") {
t-bast marked this conversation as resolved.
Show resolved Hide resolved
case class TestCase(publicIps: Seq[String], port: String, error: Option[String] = None, errorIp: Option[String] = None)
val testCases = Seq[TestCase](
TestCase(Seq("0.0.0.0", "140.82.121.4", "2620:1ec:c11:0:0:0:0:200", "2620:1ec:c11:0:0:0:0:201", "iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion", "of7husrflx7sforh3fw6yqlpwstee3wg5imvvmkp4bz6rbjxtg5nljad.onion", "acinq.co"), "9735"),
TestCase(Seq("140.82.121.4", "2620:1ec:c11:0:0:0:0:200", "acinq.fr", "iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion"), "0", Some("port 0"), Some("140.82.121.4")),
TestCase(Seq("hsmithsxurybd7uh.onion", "iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion"), "9735", Some("Tor v2"), Some("hsmithsxurybd7uh.onion")),
TestCase(Seq("acinq.co", "acinq.fr"), "9735", Some("DNS host name")),
)
testCases.foreach(test => {
val serverConf = ConfigFactory.parseMap(Map(
s"server.public-ips" -> test.publicIps.asJava,
s"server.port" -> test.port,
).asJava).withFallback(defaultConf)
val attempt = Try(makeNodeParamsWithDefaults(serverConf))
if (test.error.isEmpty) {
assert(attempt.isSuccess)
} else {
assert(attempt.isFailure)
assert(attempt.failed.get.getMessage.contains(test.error.get))
assert(test.errorIp.isEmpty || attempt.failed.get.getMessage.contains(test.errorIp.get))
}
})
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ class BlockchainWatchdogSpec extends ScalaTestWithActorTestKit(ConfigFactory.loa
useForIPv4 = true,
useForIPv6 = true,
useForTor = true,
useForWatchdogs = true)
useForWatchdogs = true,
useForDnsHostnames = true)

if (proxyAcceptsConnections(proxyParams)) {
val eventListener = TestProbe[DangerousBlocksSkew]()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ class NetworkDbSpec extends AnyFunSuite {
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-charlie", Color(100.toByte, 200.toByte, 300.toByte), Tor2("aaaqeayeaudaocaj", 42000) :: Nil, Features.empty)
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)

assert(db.listNodes().toSet === Set.empty)
db.addNode(node_1)
Expand All @@ -68,12 +69,14 @@ class NetworkDbSpec extends AnyFunSuite {
db.addNode(node_2)
db.addNode(node_3)
db.addNode(node_4)
assert(db.listNodes().toSet === Set(node_1, node_2, node_3, node_4))
db.addNode(node_5)
assert(db.listNodes().toSet === Set(node_1, node_2, node_3, node_4, node_5))
db.removeNode(node_2.nodeId)
assert(db.listNodes().toSet === Set(node_1, node_3, node_4))
assert(db.listNodes().toSet === Set(node_1, node_3, node_4, node_5))
db.updateNode(node_1)

assert(node_4.addresses == List(Tor2("aaaqeayeaudaocaj", 42000)))
assert(node_4.addresses == List(Tor3("of7husrflx7sforh3fw6yqlpwstee3wg5imvvmkp4bz6rbjxtg5nljad", 42000)))
assert(node_5.addresses == List(DnsHostname("eclair.invalid", 42000)))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ class NodeURISpec extends AnyFunSuite {
val testCases = List(
TestCase(s"$PUBKEY@$IPV4_ENDURANCE:9737", IPV4_ENDURANCE, 9737),
TestCase(s"$PUBKEY@$IPV4_ENDURANCE", IPV4_ENDURANCE, 9735),
TestCase(s"$PUBKEY@$NAME_ENDURANCE:9737", "13.248.222.197", 9737),
TestCase(s"$PUBKEY@$NAME_ENDURANCE", "13.248.222.197", 9735),
TestCase(s"$PUBKEY@$NAME_ENDURANCE:9737", NAME_ENDURANCE, 9737),
TestCase(s"$PUBKEY@$NAME_ENDURANCE", NAME_ENDURANCE, 9735),
TestCase(s"$PUBKEY@$IPV6:9737", "[2405:204:66a9:536c:873f:dc4a:f055:a298]", 9737),
TestCase(s"$PUBKEY@$IPV6", "[2405:204:66a9:536c:873f:dc4a:f055:a298]", 9735),
)
Expand Down
16 changes: 16 additions & 0 deletions eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,22 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle
mockServer.close()
}

test("return connection failure for a peer with an invalid dns host name") { f =>
import f._

// this actor listens to connection requests and creates connections
system.actorOf(ClientSpawner.props(nodeParams.keyPair, nodeParams.socksProxy_opt, nodeParams.peerConnectionConf, TestProbe().ref, TestProbe().ref))

val invalidDnsHostname_opt = NodeAddress.fromParts("eclair.invalid", 9735).toOption
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: the .invalid top level domain will never resolve.

t-bast marked this conversation as resolved.
Show resolved Hide resolved
assert(invalidDnsHostname_opt.nonEmpty)
assert(invalidDnsHostname_opt.get == DnsHostname("eclair.invalid", 9735))

val probe = TestProbe()
probe.send(peer, Peer.Init(Set.empty))
probe.send(peer, Peer.Connect(remoteNodeId, invalidDnsHostname_opt, probe.ref, isPersistent = true))
probe.expectMsgType[PeerConnection.ConnectionResult.ConnectionFailed]
}

test("successfully reconnect to peer at startup when there are existing channels", Tag("auto_reconnect")) { f =>
import f._

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,14 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers {
val ipv6LocalHost = IPAddress(InetAddress.getByAddress(Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)), 9735)
val tor2 = Tor2("aaaqeayeaudaocaj", 7777)
val tor3 = Tor3("aaaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsaijc", 9999)
val dnsHostName = DnsHostname("acinq.co", 8888)

JsonSerializers.serialization.write(ipv4)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""10.0.0.1:8888""""
JsonSerializers.serialization.write(ipv6)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""[2405:204:66a9:536c:873f:dc4a:f055:a298]:9737""""
JsonSerializers.serialization.write(ipv6LocalHost)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""[::1]:9735""""
JsonSerializers.serialization.write(tor2)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""aaaqeayeaudaocaj.onion:7777""""
JsonSerializers.serialization.write(tor3)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""aaaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsaijc.onion:9999""""
JsonSerializers.serialization.write(dnsHostName)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""acinq.co:8888""""
}

test("PeerInfo serialization") {
Expand Down
Loading