From 19acba9eed6aa13a1b4a2289dea6a67f1ed7337e Mon Sep 17 00:00:00 2001 From: t-bast Date: Wed, 30 Jun 2021 16:40:42 +0200 Subject: [PATCH 1/2] Decode warning messages Add support for logging warning messages as introduced in https://github.com/lightningnetwork/lightning-rfc/pull/834 Support for sending warning messages instead of current errors will be added in a later PR. --- .../src/main/scala/fr/acinq/eclair/io/Peer.scala | 8 +++++++- .../wire/protocol/LightningMessageCodecs.scala | 5 +++++ .../wire/protocol/LightningMessageTypes.scala | 14 ++++++++++++++ .../protocol/LightningMessageCodecsSpec.scala | 16 +++++++++++++++- 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 509980a442..68d9c94228 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -34,7 +34,7 @@ import fr.acinq.eclair.io.Monitoring.Metrics import fr.acinq.eclair.io.PeerConnection.KillReason import fr.acinq.eclair.remote.EclairInternalsSerializer.RemoteTypes import fr.acinq.eclair.wire.protocol -import fr.acinq.eclair.wire.protocol.{Error, HasChannelId, HasTemporaryChannelId, LightningMessage, NodeAddress, RoutingMessage, UnknownMessage} +import fr.acinq.eclair.wire.protocol.{Error, HasChannelId, HasTemporaryChannelId, LightningMessage, NodeAddress, RoutingMessage, UnknownMessage, Warning} import scodec.bits.ByteVector import java.net.InetSocketAddress @@ -105,6 +105,12 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: EclairWa d.peerConnection forward msg stay + case Event(warning: Warning, _: ConnectedData) => + log.warning("peer sent warning: channelId={} message={}", warning.channelId, warning.toAscii) + // NB: we don't forward warnings to the channel actors, they shouldn't take any automatic action. + // It's up to the node operator to decide what to do to address the warning. + stay + case Event(err@Error(channelId, reason), d: ConnectedData) if channelId == CHANNELID_ZERO => log.error(s"connection-level error, failing all channels! reason=${new String(reason.toArray)}") d.channels.values.toSet[ActorRef].foreach(_ forward err) // we deduplicate with toSet because there might be two entries per channel (tmp id and final id) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala index 29c29d256b..35331e2eb9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala @@ -49,6 +49,10 @@ object LightningMessageCodecs { ("channelId" | bytes32) :: ("data" | varsizebinarydata)).as[Error] + val warningCodec: Codec[Warning] = ( + ("channelId" | bytes32) :: + ("data" | varsizebinarydata)).as[Warning] + val pingCodec: Codec[Ping] = ( ("pongLength" | uint16) :: ("data" | varsizebinarydata)).as[Ping] @@ -302,6 +306,7 @@ object LightningMessageCodecs { ).as[UnknownMessage] val lightningMessageCodec = discriminated[LightningMessage].by(uint16) + .typecase(1, warningCodec) .typecase(16, initCodec) .typecase(17, errorCodec) .typecase(18, pingCodec) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index 7fda142976..5e9652ae45 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -59,6 +59,20 @@ object Error { def apply(channelId: ByteVector32, msg: String): Error = Error(channelId, ByteVector.view(msg.getBytes(Charsets.US_ASCII))) } +case class Warning(channelId: ByteVector32, data: ByteVector) extends SetupMessage with HasChannelId { + // @formatter:off + val isGlobal: Boolean = channelId == ByteVector32.Zeroes + def toAscii: String = if (fr.acinq.eclair.isAsciiPrintable(data)) new String(data.toArray, StandardCharsets.US_ASCII) else "n/a" + // @formatter:on +} + +object Warning { + // @formatter:off + def apply(channelId: ByteVector32, msg: String): Warning = Warning(channelId, ByteVector.view(msg.getBytes(Charsets.US_ASCII))) + def apply(msg: String): Warning = Warning(ByteVector32.Zeroes, ByteVector.view(msg.getBytes(Charsets.US_ASCII))) + // @formatter:on +} + case class Ping(pongLength: Int, data: ByteVector) extends SetupMessage case class Pong(data: ByteVector) extends SetupMessage diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala index da3089154d..1a59dd6a77 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala @@ -79,6 +79,20 @@ class LightningMessageCodecsSpec extends AnyFunSuite { } } + test("encode/decode warning") { + val testCases = Seq( + Warning("") -> hex"000100000000000000000000000000000000000000000000000000000000000000000000", + Warning("connection-level issue") -> hex"0x000100000000000000000000000000000000000000000000000000000000000000000016636f6e6e656374696f6e2d6c6576656c206973737565", + Warning(ByteVector32.One, "") -> hex"000101000000000000000000000000000000000000000000000000000000000000000000", + Warning(ByteVector32.One, "channel-specific issue") -> hex"0x0001010000000000000000000000000000000000000000000000000000000000000000166368616e6e656c2d7370656369666963206973737565" + ) + + for ((warning, expected) <- testCases) { + assert(lightningMessageCodec.encode(warning).require.bytes === expected) + assert(lightningMessageCodec.decode(expected.bits).require.value === warning) + } + } + test("encode/decode live node_announcements") { val ann = hex"a58338c9660d135fd7d087eb62afd24a33562c54507a9334e79f0dc4f17d407e6d7c61f0e2f3d0d38599502f61704cf1ae93608df027014ade7ff592f27ce2690001025acdf50702d2eabbbacc7c25bbd73b39e65d28237705f7bde76f557e94fb41cb18a9ec00841122116c6e302e646563656e7465722e776f726c64000000000000000000000000000000130200000000000000000000ffffae8a0b082607" val bin = ann.bits @@ -212,7 +226,7 @@ class LightningMessageCodecsSpec extends AnyFunSuite { } } - test("Unknown messages") { + test("unknown messages") { // Non-standard tag number so this message can only be handled by a codec with a fallback val unknown = UnknownMessage(tag = 47282, data = ByteVector32.Zeroes.bytes) assert(lightningMessageCodec.encode(unknown).isFailure) From ab67e7ae956ac80267dcc80506559e9a17e48bc5 Mon Sep 17 00:00:00 2001 From: t-bast Date: Thu, 1 Jul 2021 09:18:29 +0200 Subject: [PATCH 2/2] fixup! Decode warning messages --- .../src/main/scala/fr/acinq/eclair/io/Peer.scala | 2 +- .../wire/protocol/LightningMessageTypes.scala | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 68d9c94228..30a336572c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -106,7 +106,7 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: EclairWa stay case Event(warning: Warning, _: ConnectedData) => - log.warning("peer sent warning: channelId={} message={}", warning.channelId, warning.toAscii) + log.warning("peer sent warning: {}", warning.channelId, warning.toAscii) // NB: we don't forward warnings to the channel actors, they shouldn't take any automatic action. // It's up to the node operator to decide what to do to address the warning. stay diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index 5e9652ae45..2096c1037c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -51,14 +51,6 @@ case class Init(features: Features, tlvs: TlvStream[InitTlv] = TlvStream.empty) val networks = tlvs.get[InitTlv.Networks].map(_.chainHashes).getOrElse(Nil) } -case class Error(channelId: ByteVector32, data: ByteVector) extends SetupMessage with HasChannelId { - def toAscii: String = if (fr.acinq.eclair.isAsciiPrintable(data)) new String(data.toArray, StandardCharsets.US_ASCII) else "n/a" -} - -object Error { - def apply(channelId: ByteVector32, msg: String): Error = Error(channelId, ByteVector.view(msg.getBytes(Charsets.US_ASCII))) -} - case class Warning(channelId: ByteVector32, data: ByteVector) extends SetupMessage with HasChannelId { // @formatter:off val isGlobal: Boolean = channelId == ByteVector32.Zeroes @@ -73,6 +65,14 @@ object Warning { // @formatter:on } +case class Error(channelId: ByteVector32, data: ByteVector) extends SetupMessage with HasChannelId { + def toAscii: String = if (fr.acinq.eclair.isAsciiPrintable(data)) new String(data.toArray, StandardCharsets.US_ASCII) else "n/a" +} + +object Error { + def apply(channelId: ByteVector32, msg: String): Error = Error(channelId, ByteVector.view(msg.getBytes(Charsets.US_ASCII))) +} + case class Ping(pongLength: Int, data: ByteVector) extends SetupMessage case class Pong(data: ByteVector) extends SetupMessage