From f52c3dd3fca7b7f32a2740b9c866a237f4703c4f Mon Sep 17 00:00:00 2001 From: Bastien Teinturier <31281497+t-bast@users.noreply.github.com> Date: Thu, 1 Jul 2021 15:59:08 +0200 Subject: [PATCH] Decode warning messages (#1854) 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..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 @@ -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: {}", 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..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,6 +51,20 @@ case class Init(features: Features, tlvs: TlvStream[InitTlv] = TlvStream.empty) val networks = tlvs.get[InitTlv.Networks].map(_.chainHashes).getOrElse(Nil) } +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 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" } 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)