From 6f1bcb122eebd5d19f2accba337130133b1ae8c4 Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 30 Sep 2021 17:29:15 +0200 Subject: [PATCH 1/7] make custom serializers objects instead of classes --- .../acinq/eclair/json/JsonSerializers.scala | 182 +++++++++--------- .../eclair/json/JsonSerializersSpec.scala | 23 ++- 2 files changed, 102 insertions(+), 103 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index 8ba34d95df..dec534f422 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -70,100 +70,100 @@ class CustomKeySerializerOnly[A: Manifest](ser: Formats => PartialFunction[Any, def serialize(implicit format: Formats): PartialFunction[Any, String] = ser(format) } -class ByteVectorSerializer extends CustomSerializerOnly[ByteVector](_ => { +object ByteVectorSerializer extends CustomSerializerOnly[ByteVector](_ => { case x: ByteVector => JString(x.toHex) }) -class ByteVector32Serializer extends CustomSerializerOnly[ByteVector32](_ => { +object ByteVector32Serializer extends CustomSerializerOnly[ByteVector32](_ => { case x: ByteVector32 => JString(x.toHex) }) -class ByteVector32KeySerializer extends CustomKeySerializerOnly[ByteVector32](_ => { +object ByteVector32KeySerializer extends CustomKeySerializerOnly[ByteVector32](_ => { case x: ByteVector32 => x.toHex }) -class ByteVector64Serializer extends CustomSerializerOnly[ByteVector64](_ => { +object ByteVector64Serializer extends CustomSerializerOnly[ByteVector64](_ => { case x: ByteVector64 => JString(x.toHex) }) -class UInt64Serializer extends CustomSerializerOnly[UInt64](_ => { +object UInt64Serializer extends CustomSerializerOnly[UInt64](_ => { case x: UInt64 => JInt(x.toBigInt) }) -class BtcSerializer extends CustomSerializerOnly[Btc](_ => { +object BtcSerializer extends CustomSerializerOnly[Btc](_ => { case x: Btc => JDecimal(x.toDouble) }) -class SatoshiSerializer extends CustomSerializerOnly[Satoshi](_ => { +object SatoshiSerializer extends CustomSerializerOnly[Satoshi](_ => { case x: Satoshi => JInt(x.toLong) }) -class MilliSatoshiSerializer extends CustomSerializerOnly[MilliSatoshi](_ => { +object MilliSatoshiSerializer extends CustomSerializerOnly[MilliSatoshi](_ => { case x: MilliSatoshi => JInt(x.toLong) }) -class CltvExpirySerializer extends CustomSerializerOnly[CltvExpiry](_ => { +object CltvExpirySerializer extends CustomSerializerOnly[CltvExpiry](_ => { case x: CltvExpiry => JLong(x.toLong) }) -class CltvExpiryDeltaSerializer extends CustomSerializerOnly[CltvExpiryDelta](_ => { +object CltvExpiryDeltaSerializer extends CustomSerializerOnly[CltvExpiryDelta](_ => { case x: CltvExpiryDelta => JInt(x.toInt) }) -class FeeratePerKwSerializer extends CustomSerializerOnly[FeeratePerKw](_ => { +object FeeratePerKwSerializer extends CustomSerializerOnly[FeeratePerKw](_ => { case x: FeeratePerKw => JLong(x.toLong) }) -class ShortChannelIdSerializer extends CustomSerializerOnly[ShortChannelId](_ => { +object ShortChannelIdSerializer extends CustomSerializerOnly[ShortChannelId](_ => { case x: ShortChannelId => JString(x.toString) }) -class ChannelIdentifierSerializer extends CustomKeySerializerOnly[ChannelIdentifier](_ => { +object ChannelIdentifierSerializer extends CustomKeySerializerOnly[ChannelIdentifier](_ => { case Left(x: ByteVector32) => x.toHex case Right(x: ShortChannelId) => x.toString }) -class ChannelStateSerializer extends CustomSerializerOnly[ChannelState](_ => { +object ChannelStateSerializer extends CustomSerializerOnly[ChannelState](_ => { case x: ChannelState => JString(x.toString) }) -class ShaChainSerializer extends CustomSerializerOnly[ShaChain](_ => { +object ShaChainSerializer extends CustomSerializerOnly[ShaChain](_ => { case _: ShaChain => JNull }) -class PublicKeySerializer extends CustomSerializerOnly[PublicKey](_ => { +object PublicKeySerializer extends CustomSerializerOnly[PublicKey](_ => { case x: PublicKey => JString(x.toString()) }) -class PrivateKeySerializer extends CustomSerializerOnly[PrivateKey](_ => { +object PrivateKeySerializer extends CustomSerializerOnly[PrivateKey](_ => { case _: PrivateKey => JString("XXX") }) -class ChannelConfigSerializer extends CustomSerializerOnly[ChannelConfig](_ => { +object ChannelConfigSerializer extends CustomSerializerOnly[ChannelConfig](_ => { case x: ChannelConfig => JArray(x.options.toList.map(o => JString(o.name))) }) -class ChannelFeaturesSerializer extends CustomSerializerOnly[ChannelFeatures](_ => { +object ChannelFeaturesSerializer extends CustomSerializerOnly[ChannelFeatures](_ => { case channelFeatures: ChannelFeatures => JArray(channelFeatures.features.map(f => JString(f.rfcName)).toList) }) -class ChannelOpenResponseSerializer extends CustomSerializerOnly[ChannelOpenResponse](_ => { +object ChannelOpenResponseSerializer extends CustomSerializerOnly[ChannelOpenResponse](_ => { case x: ChannelOpenResponse => JString(x.toString) }) -class CommandResponseSerializer extends CustomSerializerOnly[CommandResponse[Command]](_ => { +object CommandResponseSerializer extends CustomSerializerOnly[CommandResponse[Command]](_ => { case RES_SUCCESS(_: CloseCommand, channelId) => JString(s"closed channel $channelId") case RES_FAILURE(_: Command, ex: Throwable) => JString(ex.getMessage) }) -class TransactionSerializer extends CustomSerializerOnly[TransactionWithInputInfo](_ => { +object TransactionSerializer extends CustomSerializerOnly[TransactionWithInputInfo](_ => { case x: Transaction => JObject(List( JField("txid", JString(x.txid.toHex)), JField("tx", JString(x.toString())) )) }) -class TransactionWithInputInfoSerializer extends CustomSerializerOnly[TransactionWithInputInfo](_ => { +object TransactionWithInputInfoSerializer extends CustomSerializerOnly[TransactionWithInputInfo](_ => { case x: HtlcSuccessTx => JObject(List( JField("txid", JString(x.tx.txid.toHex)), JField("tx", JString(x.tx.toString())), @@ -201,27 +201,27 @@ class TransactionWithInputInfoSerializer extends CustomSerializerOnly[Transactio )) }) -class InetSocketAddressSerializer extends CustomSerializerOnly[InetSocketAddress](_ => { +object InetSocketAddressSerializer extends CustomSerializerOnly[InetSocketAddress](_ => { case address: InetSocketAddress => JString(HostAndPort.fromParts(address.getHostString, address.getPort).toString) }) -class OutPointSerializer extends CustomSerializerOnly[OutPoint](_ => { +object OutPointSerializer extends CustomSerializerOnly[OutPoint](_ => { case x: OutPoint => JString(s"${x.txid}:${x.index}") }) -class OutPointKeySerializer extends CustomKeySerializerOnly[OutPoint](_ => { +object OutPointKeySerializer extends CustomKeySerializerOnly[OutPoint](_ => { case x: OutPoint => s"${x.txid}:${x.index}" }) -class InputInfoSerializer extends CustomSerializerOnly[InputInfo](_ => { +object InputInfoSerializer extends CustomSerializerOnly[InputInfo](_ => { case x: InputInfo => JObject(("outPoint", JString(s"${x.outPoint.txid}:${x.outPoint.index}")), ("amountSatoshis", JInt(x.txOut.amount.toLong))) }) -class ColorSerializer extends CustomSerializerOnly[Color](_ => { +object ColorSerializer extends CustomSerializerOnly[Color](_ => { case c: Color => JString(c.toString) }) -class RouteResponseSerializer extends CustomSerializerOnly[RouteResponse](_ => { +object RouteResponseSerializer extends CustomSerializerOnly[RouteResponse](_ => { case route: RouteResponse => val nodeIds = route.routes.head.hops match { case rest :+ last => rest.map(_.nodeId) :+ last.nodeId :+ last.nextNodeId @@ -230,34 +230,34 @@ class RouteResponseSerializer extends CustomSerializerOnly[RouteResponse](_ => { JArray(nodeIds.toList.map(n => JString(n.toString))) }) -class ThrowableSerializer extends CustomSerializerOnly[Throwable](_ => { +object ThrowableSerializer extends CustomSerializerOnly[Throwable](_ => { case t: Throwable if t.getMessage != null => JString(t.getMessage) case t: Throwable => JString(t.getClass.getSimpleName) }) -class FailureMessageSerializer extends CustomSerializerOnly[FailureMessage](_ => { +object FailureMessageSerializer extends CustomSerializerOnly[FailureMessage](_ => { case m: FailureMessage => JString(m.message) }) -class FailureTypeSerializer extends CustomSerializerOnly[FailureType](_ => { +object FailureTypeSerializer extends CustomSerializerOnly[FailureType](_ => { case ft: FailureType => JString(ft.toString) }) -class NodeAddressSerializer extends CustomSerializerOnly[NodeAddress](_ => { +object NodeAddressSerializer extends CustomSerializerOnly[NodeAddress](_ => { case n: NodeAddress => JString(HostAndPort.fromParts(n.socketAddress.getHostString, n.socketAddress.getPort).toString) }) -class DirectedHtlcSerializer extends CustomSerializerOnly[DirectedHtlc](_ => { +object DirectedHtlcSerializer extends CustomSerializerOnly[DirectedHtlc](_ => { case h: DirectedHtlc => new JObject(List(("direction", JString(h.direction)), ("add", Extraction.decompose(h.add)( DefaultFormats + - new ByteVector32Serializer + - new ByteVectorSerializer + - new PublicKeySerializer + - new MilliSatoshiSerializer + - new CltvExpirySerializer)))) + ByteVector32Serializer + + ByteVectorSerializer + + PublicKeySerializer + + MilliSatoshiSerializer + + CltvExpirySerializer)))) }) -class PaymentRequestSerializer extends CustomSerializerOnly[PaymentRequest](_ => { +object PaymentRequestSerializer extends CustomSerializerOnly[PaymentRequest](_ => { case p: PaymentRequest => val expiry = p.expiry.map(ex => JField("expiry", JLong(ex))).toSeq val minFinalCltvExpiry = p.minFinalCltvExpiryDelta.map(mfce => JField("minFinalCltvExpiry", JInt(mfce.toInt))).toSeq @@ -265,12 +265,12 @@ class PaymentRequestSerializer extends CustomSerializerOnly[PaymentRequest](_ => val features = JField("features", JsonSerializers.featuresToJson(Features(p.features.bitmask))) val routingInfo = JField("routingInfo", Extraction.decompose(p.routingInfo)( DefaultFormats + - new ByteVector32Serializer + - new ByteVectorSerializer + - new PublicKeySerializer + - new ShortChannelIdSerializer + - new MilliSatoshiSerializer + - new CltvExpiryDeltaSerializer + ByteVector32Serializer + + ByteVectorSerializer + + PublicKeySerializer + + ShortChannelIdSerializer + + MilliSatoshiSerializer + + CltvExpiryDeltaSerializer ) ) val fieldList = List(JField("prefix", JString(p.prefix)), @@ -288,15 +288,15 @@ class PaymentRequestSerializer extends CustomSerializerOnly[PaymentRequest](_ => JObject(fieldList) }) -class FeaturesSerializer extends CustomSerializerOnly[Features](_ => { +object FeaturesSerializer extends CustomSerializerOnly[Features](_ => { case features: Features => JsonSerializers.featuresToJson(features) }) -class JavaUUIDSerializer extends CustomSerializerOnly[UUID](_ => { +object JavaUUIDSerializer extends CustomSerializerOnly[UUID](_ => { case id: UUID => JString(id.toString) }) -class ChannelEventSerializer extends CustomSerializerOnly[ChannelEvent](_ => { +object ChannelEventSerializer extends CustomSerializerOnly[ChannelEvent](_ => { case e: ChannelCreated => JObject( JField("type", JString("channel-opened")), JField("remoteNodeId", JString(e.remoteNodeId.toString())), @@ -319,7 +319,7 @@ class ChannelEventSerializer extends CustomSerializerOnly[ChannelEvent](_ => { ) }) -class OriginSerializer extends CustomSerializerOnly[Origin](_ => { +object OriginSerializer extends CustomSerializerOnly[Origin](_ => { case o: Origin.Local => JObject(JField("paymentId", JString(o.id.toString))) case o: Origin.ChannelRelayed => JObject( JField("channelId", JString(o.originChannelId.toHex)), @@ -333,9 +333,9 @@ class OriginSerializer extends CustomSerializerOnly[Origin](_ => { }) }) -class GlobalBalanceSerializer extends CustomSerializerOnly[GlobalBalance](_ => { +object GlobalBalanceSerializer extends CustomSerializerOnly[GlobalBalance](_ => { case o: GlobalBalance => - val formats = DefaultFormats + new ByteVector32KeySerializer + new BtcSerializer + new SatoshiSerializer + val formats = DefaultFormats + ByteVector32KeySerializer + BtcSerializer + SatoshiSerializer JObject(JField("total", JDecimal(o.total.toDouble))) merge Extraction.decompose(o)(formats) }) @@ -396,45 +396,45 @@ object JsonSerializers { implicit val serialization: Serialization.type = jackson.Serialization implicit val formats: Formats = (org.json4s.DefaultFormats + - new ByteVectorSerializer + - new ByteVector32Serializer + - new ByteVector64Serializer + - new ChannelEventSerializer + - new UInt64Serializer + - new BtcSerializer + - new SatoshiSerializer + - new MilliSatoshiSerializer + - new CltvExpirySerializer + - new CltvExpiryDeltaSerializer + - new FeeratePerKwSerializer + - new ShortChannelIdSerializer + - new ChannelIdentifierSerializer + - new ChannelStateSerializer + - new ShaChainSerializer + - new PublicKeySerializer + - new PrivateKeySerializer + - new TransactionSerializer + - new TransactionWithInputInfoSerializer + - new InetSocketAddressSerializer + - new OutPointSerializer + - new OutPointKeySerializer + - new ChannelConfigSerializer + - new ChannelFeaturesSerializer + - new ChannelOpenResponseSerializer + - new CommandResponseSerializer + - new InputInfoSerializer + - new ColorSerializer + - new RouteResponseSerializer + - new ThrowableSerializer + - new FailureMessageSerializer + - new FailureTypeSerializer + - new NodeAddressSerializer + - new DirectedHtlcSerializer + - new PaymentRequestSerializer + - new JavaUUIDSerializer + - new FeaturesSerializer + - new OriginSerializer + - new GlobalBalanceSerializer + + ByteVectorSerializer + + ByteVector32Serializer + + ByteVector64Serializer + + ChannelEventSerializer + + UInt64Serializer + + BtcSerializer + + SatoshiSerializer + + MilliSatoshiSerializer + + CltvExpirySerializer + + CltvExpiryDeltaSerializer + + FeeratePerKwSerializer + + ShortChannelIdSerializer + + ChannelIdentifierSerializer + + ChannelStateSerializer + + ShaChainSerializer + + PublicKeySerializer + + PrivateKeySerializer + + TransactionSerializer + + TransactionWithInputInfoSerializer + + InetSocketAddressSerializer + + OutPointSerializer + + OutPointKeySerializer + + ChannelConfigSerializer + + ChannelFeaturesSerializer + + ChannelOpenResponseSerializer + + CommandResponseSerializer + + InputInfoSerializer + + ColorSerializer + + RouteResponseSerializer + + ThrowableSerializer + + FailureMessageSerializer + + FailureTypeSerializer + + NodeAddressSerializer + + DirectedHtlcSerializer + + PaymentRequestSerializer + + JavaUUIDSerializer + + FeaturesSerializer + + OriginSerializer + + GlobalBalanceSerializer + CustomTypeHints.incomingPaymentStatus + CustomTypeHints.outgoingPaymentStatus + CustomTypeHints.paymentEvent + diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index 11f3ae52d2..5d23fde61d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -18,7 +18,6 @@ package fr.acinq.eclair.json import fr.acinq.bitcoin.{Btc, ByteVector32, OutPoint, Satoshi, Transaction, TxOut} import fr.acinq.eclair._ -import fr.acinq.eclair.json._ import fr.acinq.eclair.balance.CheckBalance import fr.acinq.eclair.balance.CheckBalance.{ClosingBalance, GlobalBalance, MainAndHtlcBalance, PossiblyPublishedMainAndHtlcBalance, PossiblyPublishedMainBalance} import fr.acinq.eclair.channel.Origin @@ -51,7 +50,7 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { assert(error.msg.contains("Do not know how to serialize key of type class fr.acinq.bitcoin.OutPoint.")) // but it works with our custom key serializer - val json = JsonSerializers.serialization.write(map)(org.json4s.DefaultFormats + new ByteVectorSerializer + new OutPointKeySerializer) + val json = JsonSerializers.serialization.write(map)(org.json4s.DefaultFormats + ByteVectorSerializer + OutPointKeySerializer) assertJsonEquals(json, s"""{"${output1.txid}:0":"dead","${output2.txid}:1":"beef"}""") } @@ -70,7 +69,7 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { assert(error.msg.contains("Do not know how to serialize key of type class fr.acinq.bitcoin.OutPoint.")) // but it works with our custom key serializer - val json = JsonSerializers.serialization.write(map)(org.json4s.DefaultFormats + new TransactionSerializer + new OutPointKeySerializer) + val json = JsonSerializers.serialization.write(map)(org.json4s.DefaultFormats + TransactionSerializer + OutPointKeySerializer) val expectedJson = s"""{ | "${output1.txid}:0": { @@ -92,10 +91,10 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { val tor2 = Tor2("aaaqeayeaudaocaj", 7777) val tor3 = Tor3("aaaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsaijc", 9999) - JsonSerializers.serialization.write(ipv4)(org.json4s.DefaultFormats + new NodeAddressSerializer) shouldBe s""""10.0.0.1:8888"""" - JsonSerializers.serialization.write(ipv6LocalHost)(org.json4s.DefaultFormats + new NodeAddressSerializer) shouldBe s""""[0:0:0:0:0:0:0:1]:9735"""" - JsonSerializers.serialization.write(tor2)(org.json4s.DefaultFormats + new NodeAddressSerializer) shouldBe s""""aaaqeayeaudaocaj.onion:7777"""" - JsonSerializers.serialization.write(tor3)(org.json4s.DefaultFormats + new NodeAddressSerializer) shouldBe s""""aaaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsaijc.onion:9999"""" + JsonSerializers.serialization.write(ipv4)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""10.0.0.1:8888"""" + JsonSerializers.serialization.write(ipv6LocalHost)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""[0:0:0:0:0:0:0: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"""" } test("DirectedHtlc serialization") { @@ -115,22 +114,22 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { val expectedIn = """{"direction":"IN","add":{"channelId":"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f","id":926,"amountMsat":12365,"paymentHash":"9fcd45bbaa09c60c991ac0425704163c3f3d2d683c789fa409455b9c97792692","cltvExpiry":621500,"onionRoutingPacket":{"version":0,"publicKey":"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b","payload":"3c7a66997c681a3de1bae56438abeee4fc50a16554725a430ade1dc8db6bdd76704d45c6151c4051d710cf487e63f8cbe9f5e537e6e518d7998f11c40277d551bee036d227a421f344c0185b4533660d200acbc4f4aa6148e29f813bb537e950a79ba961b80ccaa6ad808cb88f858ee73f8b1f129d3214d194f76fc011c46e18c2caec0fcb69715e79cb381e449e5be20281e0aaa92defa089c98b7e29c22181f2d1af9f5fe2a37ede4ac3163b123aa72318201b1128b17053c381e7bf111620dfb7ea3dcc5c28cafdd9cb7bb1a4202b64199aa02af145c563ba1b9f10288203ce2666f486aacb2ee385dc0b67915864c95174dfaac1e0ea195329d1741cd1febb4b49b33f84e0d10a5ec8ee0a1a94f73abf081ec69bb863edfeb46d24ce424b025ac3f593a7ba9419ec9b17fb39f0fbeac80e0898f95b6709cbc95f7c097e22e3a6ca0efbc947bbcd4a9077f6bd9daba25b18bb16179fca9d9cb2ce49fc7cbd2de589237926cacb87ea60e2cc60a90b47575517921b5529b8a95823dd0c3d02a7747d74c4ca927ba6b70c06c1c1ef27e14d371e8dd8f5d9380a65b08ae1e6384f9b3575c5d7278de22ce80e63612a27f3b3f45dbe32ee855185293c719e5a7203a682a08fd810c46fa12b67e61349831f8fae3f558090ea988e1a22ec877b790ea09169055529247c4dd597857aad74eaeb3a5879e96453e681e213f2796ed704d620509f34f91d9d16f881fd397e2836a0a4d2f1bcd230067f7acb5381a2b17e8c5135e38c4d258afbe4f69ac7ad39b789e99686ee926b3ad31b98993673313b7b18a4faaea238d8055824fde7339a791fc7777ef28cc4a1a5d177b3c3882ced4921c6cd85ae82e1fe13fe680ae432a9918ce37b15f88d4d18fb16b69e5369d18c204aaf7ee49b830bf2328380e6ad96e8f6a9e01bc2c97ffbaa402d5406dc7b83c6eb5d515ffc3bea8c42cf299c9e2bea693515246c6ff859d33ba6c4da4c4c1706e0c6b4a574e927f02eb92b7d56722cff80c3e6f6b98d1c84cb576abdcc27a6bc7b110fc2ac5fead57f05ad854c3331ce1ff94c0dc97303540ee797d71566497af09f20e3554d467528e1fed8e69438171072fe2deca3979a8f5ec9043b9bc4da921b095c29dc0294148c1b7001dafda4c48600d1194f745e6d0689c561bf19d20758c4d25fac64d81780607a4106e220ef546fc4026af7b9da8defb2fe3c21d48798ac67c794fb40aabe44618a8911673466be06808c6f54a772b87bcfafb4d120a9bebffb8051bf24bb332eaa769cf175c1aadb0186f8946dc32513fd81fe1a61bfb860886bdd070359a43e06e74607d300bd2e01a3f1ee900c4039e8db742170228db61ef0c77724c49d1573144564a80cc1ebc0449b34f84be35187ceba3fbc2facf5ad1f1e15945e3c6c236579aca7bc97e4cc76a3310022693b64008562b254a7d11c0086813e551c4817bbb72a1d6fbfc84d326ce973651200f80aa8ab0976c53c390249ca8e7e5ec21b80e70c3e0205983d313b28a5d1b9d9149501e05d3257c8ae88c6308e9e00feeab19121d1032a582e68ca1f9f64a1fd91cb5d8613b985fd4be22a4d5c14a132c20811a75ee3cc61de0b3fbc3254d61995d086603032269888b942ec0971ad26ea4b8df1746c5ec1de904ddeb5045abc0a6ede9d6a199ed0782cb69efa3a4dc00747553dbef12fb8299ca364b4cdf2ac3eba03b0d8b273684116bba4458b5717bc4aca5406901173a89b3643ccc076f22779fccf1ad69981e24eef18c711a2d58dfe5834b41f9e7166d54dc8628e754baaca1cbb7db8256f88ebc889de6078ba83a1af14a4","hmac":"9442626f72c475963dbddf8a57ab2cef3013eb3d6a5e8afbea9e631dac4481f5"},"tlvStream":{"records":[],"unknown":[]}}}""" - JsonSerializers.serialization.write(IncomingHtlc(add))(org.json4s.DefaultFormats + new DirectedHtlcSerializer) shouldBe expectedIn - JsonSerializers.serialization.write(OutgoingHtlc(add))(org.json4s.DefaultFormats + new DirectedHtlcSerializer) shouldBe expectedIn.replace("IN", "OUT") + JsonSerializers.serialization.write(IncomingHtlc(add))(org.json4s.DefaultFormats + DirectedHtlcSerializer) shouldBe expectedIn + JsonSerializers.serialization.write(OutgoingHtlc(add))(org.json4s.DefaultFormats + DirectedHtlcSerializer) shouldBe expectedIn.replace("IN", "OUT") } test("HTLC origin serialization") { val localOrigin = Origin.LocalCold(UUID.fromString("11111111-1111-1111-1111-111111111111")) val expectedLocalOrigin = """{"paymentId":"11111111-1111-1111-1111-111111111111"}""" - JsonSerializers.serialization.write(localOrigin)(org.json4s.DefaultFormats + new OriginSerializer) shouldBe expectedLocalOrigin + JsonSerializers.serialization.write(localOrigin)(org.json4s.DefaultFormats + OriginSerializer) shouldBe expectedLocalOrigin val channelOrigin = Origin.ChannelRelayedCold(ByteVector32(hex"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f"), 7, 500 msat, 400 msat) val expectedChannelOrigin = """{"channelId":"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f","htlcId":7}""" - JsonSerializers.serialization.write(channelOrigin)(org.json4s.DefaultFormats + new OriginSerializer) shouldBe expectedChannelOrigin + JsonSerializers.serialization.write(channelOrigin)(org.json4s.DefaultFormats + OriginSerializer) shouldBe expectedChannelOrigin val trampolineOrigin = Origin.TrampolineRelayedCold((ByteVector32(hex"9fcd45bbaa09c60c991ac0425704163c3f3d2d683c789fa409455b9c97792692"), 3L) :: (ByteVector32(hex"70685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b"), 7L) :: Nil) val expectedTrampolineOrigin = """[{"channelId":"9fcd45bbaa09c60c991ac0425704163c3f3d2d683c789fa409455b9c97792692","htlcId":3},{"channelId":"70685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b","htlcId":7}]""" - JsonSerializers.serialization.write(trampolineOrigin)(org.json4s.DefaultFormats + new OriginSerializer) shouldBe expectedTrampolineOrigin + JsonSerializers.serialization.write(trampolineOrigin)(org.json4s.DefaultFormats + OriginSerializer) shouldBe expectedTrampolineOrigin } test("Payment Request") { From 00d89475497b9c34ce54194e7b4e9220da0a1e82 Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 30 Sep 2021 17:30:43 +0200 Subject: [PATCH 2/7] nits: reorder json format definition --- .../scala/fr/acinq/eclair/json/JsonSerializers.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index dec534f422..04a4c63449 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -395,7 +395,11 @@ object JsonSerializers { implicit val serialization: Serialization.type = jackson.Serialization - implicit val formats: Formats = (org.json4s.DefaultFormats + + implicit val formats: Formats = org.json4s.DefaultFormats.withTypeHintFieldName("type") + + CustomTypeHints.incomingPaymentStatus + + CustomTypeHints.outgoingPaymentStatus + + CustomTypeHints.paymentEvent + + CustomTypeHints.channelStates + ByteVectorSerializer + ByteVector32Serializer + ByteVector64Serializer + @@ -434,11 +438,7 @@ object JsonSerializers { JavaUUIDSerializer + FeaturesSerializer + OriginSerializer + - GlobalBalanceSerializer + - CustomTypeHints.incomingPaymentStatus + - CustomTypeHints.outgoingPaymentStatus + - CustomTypeHints.paymentEvent + - CustomTypeHints.channelStates).withTypeHintFieldName("type") + GlobalBalanceSerializer def featuresToJson(features: Features): JObject = JObject( JField("activated", JObject(features.activated.map { case (feature, support) => From fdc2d6dbe0bdc770519ea9f2f6186985553b04a9 Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 30 Sep 2021 19:06:10 +0200 Subject: [PATCH 3/7] use minimal serializers Having custom serializers depend on external format would introduce an infinite recursion at runtime if not careful. Thankfully, none of our serializers use it, so we may as well remove the possibility entirely. --- .../acinq/eclair/json/JsonSerializers.scala | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index 04a4c63449..7e57436002 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -43,11 +43,11 @@ import java.net.InetSocketAddress import java.util.UUID /** - * Custom serializer that only does serialization, not deserialization. + * Minimal serializer that only does serialization, not deserialization, and does not depend on external formats. * * NB: this is a stripped-down version of [[org.json4s.CustomSerializer]] */ -class CustomSerializerOnly[A: Manifest](ser: Formats => PartialFunction[Any, JValue]) extends Serializer[A] { +class MinimalSerializer[A: Manifest](ser: PartialFunction[Any, JValue]) extends Serializer[A] { val Class: Class[_] = implicitly[Manifest[A]].runtimeClass @@ -55,115 +55,115 @@ class CustomSerializerOnly[A: Manifest](ser: Formats => PartialFunction[Any, JVa case (TypeInfo(Class, _), json) => throw new MappingException("Can't convert " + json + " to " + Class) } - def serialize(implicit format: Formats): PartialFunction[Any, JValue] = ser(format) + def serialize(implicit format: Formats): PartialFunction[Any, JValue] = ser } /** Same as above, but for [[org.json4s.CustomKeySerializer]] */ -class CustomKeySerializerOnly[A: Manifest](ser: Formats => PartialFunction[Any, String]) extends KeySerializer[A] { +class MinimalKeySerializer[A: Manifest](ser: PartialFunction[Any, String]) extends KeySerializer[A] { - val Class = implicitly[Manifest[A]].runtimeClass + val Class: Class[_] = implicitly[Manifest[A]].runtimeClass def deserialize(implicit format: Formats): PartialFunction[(json4s.TypeInfo, String), A] = { case (TypeInfo(Class, _), json) => throw new MappingException("Can't convert " + json + " to " + Class) } - def serialize(implicit format: Formats): PartialFunction[Any, String] = ser(format) + def serialize(implicit format: Formats): PartialFunction[Any, String] = ser } -object ByteVectorSerializer extends CustomSerializerOnly[ByteVector](_ => { +object ByteVectorSerializer extends MinimalSerializer[ByteVector]({ case x: ByteVector => JString(x.toHex) }) -object ByteVector32Serializer extends CustomSerializerOnly[ByteVector32](_ => { +object ByteVector32Serializer extends MinimalSerializer[ByteVector32]({ case x: ByteVector32 => JString(x.toHex) }) -object ByteVector32KeySerializer extends CustomKeySerializerOnly[ByteVector32](_ => { +object ByteVector32KeySerializer extends MinimalKeySerializer[ByteVector32]({ case x: ByteVector32 => x.toHex }) -object ByteVector64Serializer extends CustomSerializerOnly[ByteVector64](_ => { +object ByteVector64Serializer extends MinimalSerializer[ByteVector64]({ case x: ByteVector64 => JString(x.toHex) }) -object UInt64Serializer extends CustomSerializerOnly[UInt64](_ => { +object UInt64Serializer extends MinimalSerializer[UInt64]({ case x: UInt64 => JInt(x.toBigInt) }) -object BtcSerializer extends CustomSerializerOnly[Btc](_ => { +object BtcSerializer extends MinimalSerializer[Btc]({ case x: Btc => JDecimal(x.toDouble) }) -object SatoshiSerializer extends CustomSerializerOnly[Satoshi](_ => { +object SatoshiSerializer extends MinimalSerializer[Satoshi]({ case x: Satoshi => JInt(x.toLong) }) -object MilliSatoshiSerializer extends CustomSerializerOnly[MilliSatoshi](_ => { +object MilliSatoshiSerializer extends MinimalSerializer[MilliSatoshi]({ case x: MilliSatoshi => JInt(x.toLong) }) -object CltvExpirySerializer extends CustomSerializerOnly[CltvExpiry](_ => { +object CltvExpirySerializer extends MinimalSerializer[CltvExpiry]({ case x: CltvExpiry => JLong(x.toLong) }) -object CltvExpiryDeltaSerializer extends CustomSerializerOnly[CltvExpiryDelta](_ => { +object CltvExpiryDeltaSerializer extends MinimalSerializer[CltvExpiryDelta]({ case x: CltvExpiryDelta => JInt(x.toInt) }) -object FeeratePerKwSerializer extends CustomSerializerOnly[FeeratePerKw](_ => { +object FeeratePerKwSerializer extends MinimalSerializer[FeeratePerKw]({ case x: FeeratePerKw => JLong(x.toLong) }) -object ShortChannelIdSerializer extends CustomSerializerOnly[ShortChannelId](_ => { +object ShortChannelIdSerializer extends MinimalSerializer[ShortChannelId]({ case x: ShortChannelId => JString(x.toString) }) -object ChannelIdentifierSerializer extends CustomKeySerializerOnly[ChannelIdentifier](_ => { +object ChannelIdentifierSerializer extends MinimalKeySerializer[ChannelIdentifier]({ case Left(x: ByteVector32) => x.toHex case Right(x: ShortChannelId) => x.toString }) -object ChannelStateSerializer extends CustomSerializerOnly[ChannelState](_ => { +object ChannelStateSerializer extends MinimalSerializer[ChannelState]({ case x: ChannelState => JString(x.toString) }) -object ShaChainSerializer extends CustomSerializerOnly[ShaChain](_ => { +object ShaChainSerializer extends MinimalSerializer[ShaChain]({ case _: ShaChain => JNull }) -object PublicKeySerializer extends CustomSerializerOnly[PublicKey](_ => { +object PublicKeySerializer extends MinimalSerializer[PublicKey]({ case x: PublicKey => JString(x.toString()) }) -object PrivateKeySerializer extends CustomSerializerOnly[PrivateKey](_ => { +object PrivateKeySerializer extends MinimalSerializer[PrivateKey]({ case _: PrivateKey => JString("XXX") }) -object ChannelConfigSerializer extends CustomSerializerOnly[ChannelConfig](_ => { +object ChannelConfigSerializer extends MinimalSerializer[ChannelConfig]({ case x: ChannelConfig => JArray(x.options.toList.map(o => JString(o.name))) }) -object ChannelFeaturesSerializer extends CustomSerializerOnly[ChannelFeatures](_ => { +object ChannelFeaturesSerializer extends MinimalSerializer[ChannelFeatures]({ case channelFeatures: ChannelFeatures => JArray(channelFeatures.features.map(f => JString(f.rfcName)).toList) }) -object ChannelOpenResponseSerializer extends CustomSerializerOnly[ChannelOpenResponse](_ => { +object ChannelOpenResponseSerializer extends MinimalSerializer[ChannelOpenResponse]({ case x: ChannelOpenResponse => JString(x.toString) }) -object CommandResponseSerializer extends CustomSerializerOnly[CommandResponse[Command]](_ => { +object CommandResponseSerializer extends MinimalSerializer[CommandResponse[Command]]({ case RES_SUCCESS(_: CloseCommand, channelId) => JString(s"closed channel $channelId") case RES_FAILURE(_: Command, ex: Throwable) => JString(ex.getMessage) }) -object TransactionSerializer extends CustomSerializerOnly[TransactionWithInputInfo](_ => { +object TransactionSerializer extends MinimalSerializer[TransactionWithInputInfo]({ case x: Transaction => JObject(List( JField("txid", JString(x.txid.toHex)), JField("tx", JString(x.toString())) )) }) -object TransactionWithInputInfoSerializer extends CustomSerializerOnly[TransactionWithInputInfo](_ => { +object TransactionWithInputInfoSerializer extends MinimalSerializer[TransactionWithInputInfo]({ case x: HtlcSuccessTx => JObject(List( JField("txid", JString(x.tx.txid.toHex)), JField("tx", JString(x.tx.toString())), @@ -201,27 +201,27 @@ object TransactionWithInputInfoSerializer extends CustomSerializerOnly[Transacti )) }) -object InetSocketAddressSerializer extends CustomSerializerOnly[InetSocketAddress](_ => { +object InetSocketAddressSerializer extends MinimalSerializer[InetSocketAddress]({ case address: InetSocketAddress => JString(HostAndPort.fromParts(address.getHostString, address.getPort).toString) }) -object OutPointSerializer extends CustomSerializerOnly[OutPoint](_ => { +object OutPointSerializer extends MinimalSerializer[OutPoint]({ case x: OutPoint => JString(s"${x.txid}:${x.index}") }) -object OutPointKeySerializer extends CustomKeySerializerOnly[OutPoint](_ => { +object OutPointKeySerializer extends MinimalKeySerializer[OutPoint]({ case x: OutPoint => s"${x.txid}:${x.index}" }) -object InputInfoSerializer extends CustomSerializerOnly[InputInfo](_ => { +object InputInfoSerializer extends MinimalSerializer[InputInfo]({ case x: InputInfo => JObject(("outPoint", JString(s"${x.outPoint.txid}:${x.outPoint.index}")), ("amountSatoshis", JInt(x.txOut.amount.toLong))) }) -object ColorSerializer extends CustomSerializerOnly[Color](_ => { +object ColorSerializer extends MinimalSerializer[Color]({ case c: Color => JString(c.toString) }) -object RouteResponseSerializer extends CustomSerializerOnly[RouteResponse](_ => { +object RouteResponseSerializer extends MinimalSerializer[RouteResponse]({ case route: RouteResponse => val nodeIds = route.routes.head.hops match { case rest :+ last => rest.map(_.nodeId) :+ last.nodeId :+ last.nextNodeId @@ -230,24 +230,24 @@ object RouteResponseSerializer extends CustomSerializerOnly[RouteResponse](_ => JArray(nodeIds.toList.map(n => JString(n.toString))) }) -object ThrowableSerializer extends CustomSerializerOnly[Throwable](_ => { +object ThrowableSerializer extends MinimalSerializer[Throwable]({ case t: Throwable if t.getMessage != null => JString(t.getMessage) case t: Throwable => JString(t.getClass.getSimpleName) }) -object FailureMessageSerializer extends CustomSerializerOnly[FailureMessage](_ => { +object FailureMessageSerializer extends MinimalSerializer[FailureMessage]({ case m: FailureMessage => JString(m.message) }) -object FailureTypeSerializer extends CustomSerializerOnly[FailureType](_ => { +object FailureTypeSerializer extends MinimalSerializer[FailureType]({ case ft: FailureType => JString(ft.toString) }) -object NodeAddressSerializer extends CustomSerializerOnly[NodeAddress](_ => { +object NodeAddressSerializer extends MinimalSerializer[NodeAddress]({ case n: NodeAddress => JString(HostAndPort.fromParts(n.socketAddress.getHostString, n.socketAddress.getPort).toString) }) -object DirectedHtlcSerializer extends CustomSerializerOnly[DirectedHtlc](_ => { +object DirectedHtlcSerializer extends MinimalSerializer[DirectedHtlc]({ case h: DirectedHtlc => new JObject(List(("direction", JString(h.direction)), ("add", Extraction.decompose(h.add)( DefaultFormats + ByteVector32Serializer + @@ -257,7 +257,7 @@ object DirectedHtlcSerializer extends CustomSerializerOnly[DirectedHtlc](_ => { CltvExpirySerializer)))) }) -object PaymentRequestSerializer extends CustomSerializerOnly[PaymentRequest](_ => { +object PaymentRequestSerializer extends MinimalSerializer[PaymentRequest]({ case p: PaymentRequest => val expiry = p.expiry.map(ex => JField("expiry", JLong(ex))).toSeq val minFinalCltvExpiry = p.minFinalCltvExpiryDelta.map(mfce => JField("minFinalCltvExpiry", JInt(mfce.toInt))).toSeq @@ -271,7 +271,7 @@ object PaymentRequestSerializer extends CustomSerializerOnly[PaymentRequest](_ = ShortChannelIdSerializer + MilliSatoshiSerializer + CltvExpiryDeltaSerializer - ) + ) ) val fieldList = List(JField("prefix", JString(p.prefix)), JField("timestamp", JLong(p.timestamp)), @@ -288,15 +288,15 @@ object PaymentRequestSerializer extends CustomSerializerOnly[PaymentRequest](_ = JObject(fieldList) }) -object FeaturesSerializer extends CustomSerializerOnly[Features](_ => { +object FeaturesSerializer extends MinimalSerializer[Features]({ case features: Features => JsonSerializers.featuresToJson(features) }) -object JavaUUIDSerializer extends CustomSerializerOnly[UUID](_ => { +object JavaUUIDSerializer extends MinimalSerializer[UUID]({ case id: UUID => JString(id.toString) }) -object ChannelEventSerializer extends CustomSerializerOnly[ChannelEvent](_ => { +object ChannelEventSerializer extends MinimalSerializer[ChannelEvent]({ case e: ChannelCreated => JObject( JField("type", JString("channel-opened")), JField("remoteNodeId", JString(e.remoteNodeId.toString())), @@ -319,7 +319,7 @@ object ChannelEventSerializer extends CustomSerializerOnly[ChannelEvent](_ => { ) }) -object OriginSerializer extends CustomSerializerOnly[Origin](_ => { +object OriginSerializer extends MinimalSerializer[Origin]({ case o: Origin.Local => JObject(JField("paymentId", JString(o.id.toString))) case o: Origin.ChannelRelayed => JObject( JField("channelId", JString(o.originChannelId.toHex)), @@ -333,7 +333,7 @@ object OriginSerializer extends CustomSerializerOnly[Origin](_ => { }) }) -object GlobalBalanceSerializer extends CustomSerializerOnly[GlobalBalance](_ => { +object GlobalBalanceSerializer extends MinimalSerializer[GlobalBalance]({ case o: GlobalBalance => val formats = DefaultFormats + ByteVector32KeySerializer + BtcSerializer + SatoshiSerializer JObject(JField("total", JDecimal(o.total.toDouble))) merge Extraction.decompose(o)(formats) From 5d0247461215c5566776d914d5e128be09a2b04f Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 1 Oct 2021 11:02:32 +0200 Subject: [PATCH 4/7] simplify serializers further We don't need to type the serializers: this is required for deserializing, not serializing, and we are not using it. The fact that be had a type mismatch here shows it: ```scala object TransactionSerializer extends MinimalSerializer[TransactionWithInputInfo] ``` --- .../acinq/eclair/json/JsonSerializers.scala | 100 ++++++++---------- 1 file changed, 45 insertions(+), 55 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index 7e57436002..30ff17b468 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -19,7 +19,6 @@ package fr.acinq.eclair.json import com.google.common.net.HostAndPort import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.{Btc, ByteVector32, ByteVector64, OutPoint, Satoshi, Transaction} -import fr.acinq.eclair.ApiTypes.ChannelIdentifier import fr.acinq.eclair.balance.CheckBalance.GlobalBalance import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel._ @@ -35,8 +34,7 @@ import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, Sho import org.json4s import org.json4s.JsonAST._ import org.json4s.jackson.Serialization -import org.json4s.reflect.TypeInfo -import org.json4s.{DefaultFormats, Extraction, Formats, JDecimal, JValue, KeySerializer, MappingException, Serializer, ShortTypeHints, TypeHints, jackson} +import org.json4s.{DefaultFormats, Extraction, Formats, JDecimal, JValue, KeySerializer, Serializer, ShortTypeHints, TypeHints, jackson} import scodec.bits.ByteVector import java.net.InetSocketAddress @@ -47,123 +45,115 @@ import java.util.UUID * * NB: this is a stripped-down version of [[org.json4s.CustomSerializer]] */ -class MinimalSerializer[A: Manifest](ser: PartialFunction[Any, JValue]) extends Serializer[A] { +class MinimalSerializer(ser: PartialFunction[Any, JValue]) extends Serializer[Nothing] { - val Class: Class[_] = implicitly[Manifest[A]].runtimeClass - - def deserialize(implicit format: Formats): PartialFunction[(json4s.TypeInfo, JValue), A] = { - case (TypeInfo(Class, _), json) => throw new MappingException("Can't convert " + json + " to " + Class) - } + def deserialize(implicit format: Formats): PartialFunction[(json4s.TypeInfo, JValue), Nothing] = PartialFunction.empty def serialize(implicit format: Formats): PartialFunction[Any, JValue] = ser } /** Same as above, but for [[org.json4s.CustomKeySerializer]] */ -class MinimalKeySerializer[A: Manifest](ser: PartialFunction[Any, String]) extends KeySerializer[A] { - - val Class: Class[_] = implicitly[Manifest[A]].runtimeClass +class MinimalKeySerializer(ser: PartialFunction[Any, String]) extends KeySerializer[Nothing] { - def deserialize(implicit format: Formats): PartialFunction[(json4s.TypeInfo, String), A] = { - case (TypeInfo(Class, _), json) => throw new MappingException("Can't convert " + json + " to " + Class) - } + def deserialize(implicit format: Formats): PartialFunction[(json4s.TypeInfo, String), Nothing] = PartialFunction.empty def serialize(implicit format: Formats): PartialFunction[Any, String] = ser } -object ByteVectorSerializer extends MinimalSerializer[ByteVector]({ +object ByteVectorSerializer extends MinimalSerializer({ case x: ByteVector => JString(x.toHex) }) -object ByteVector32Serializer extends MinimalSerializer[ByteVector32]({ +object ByteVector32Serializer extends MinimalSerializer({ case x: ByteVector32 => JString(x.toHex) }) -object ByteVector32KeySerializer extends MinimalKeySerializer[ByteVector32]({ +object ByteVector32KeySerializer extends MinimalKeySerializer({ case x: ByteVector32 => x.toHex }) -object ByteVector64Serializer extends MinimalSerializer[ByteVector64]({ +object ByteVector64Serializer extends MinimalSerializer({ case x: ByteVector64 => JString(x.toHex) }) -object UInt64Serializer extends MinimalSerializer[UInt64]({ +object UInt64Serializer extends MinimalSerializer({ case x: UInt64 => JInt(x.toBigInt) }) -object BtcSerializer extends MinimalSerializer[Btc]({ +object BtcSerializer extends MinimalSerializer({ case x: Btc => JDecimal(x.toDouble) }) -object SatoshiSerializer extends MinimalSerializer[Satoshi]({ +object SatoshiSerializer extends MinimalSerializer({ case x: Satoshi => JInt(x.toLong) }) -object MilliSatoshiSerializer extends MinimalSerializer[MilliSatoshi]({ +object MilliSatoshiSerializer extends MinimalSerializer({ case x: MilliSatoshi => JInt(x.toLong) }) -object CltvExpirySerializer extends MinimalSerializer[CltvExpiry]({ +object CltvExpirySerializer extends MinimalSerializer({ case x: CltvExpiry => JLong(x.toLong) }) -object CltvExpiryDeltaSerializer extends MinimalSerializer[CltvExpiryDelta]({ +object CltvExpiryDeltaSerializer extends MinimalSerializer({ case x: CltvExpiryDelta => JInt(x.toInt) }) -object FeeratePerKwSerializer extends MinimalSerializer[FeeratePerKw]({ +object FeeratePerKwSerializer extends MinimalSerializer({ case x: FeeratePerKw => JLong(x.toLong) }) -object ShortChannelIdSerializer extends MinimalSerializer[ShortChannelId]({ +object ShortChannelIdSerializer extends MinimalSerializer({ case x: ShortChannelId => JString(x.toString) }) -object ChannelIdentifierSerializer extends MinimalKeySerializer[ChannelIdentifier]({ +object ChannelIdentifierSerializer extends MinimalKeySerializer({ case Left(x: ByteVector32) => x.toHex case Right(x: ShortChannelId) => x.toString }) -object ChannelStateSerializer extends MinimalSerializer[ChannelState]({ +object ChannelStateSerializer extends MinimalSerializer({ case x: ChannelState => JString(x.toString) }) -object ShaChainSerializer extends MinimalSerializer[ShaChain]({ +object ShaChainSerializer extends MinimalSerializer({ case _: ShaChain => JNull }) -object PublicKeySerializer extends MinimalSerializer[PublicKey]({ +object PublicKeySerializer extends MinimalSerializer({ case x: PublicKey => JString(x.toString()) }) -object PrivateKeySerializer extends MinimalSerializer[PrivateKey]({ +object PrivateKeySerializer extends MinimalSerializer({ case _: PrivateKey => JString("XXX") }) -object ChannelConfigSerializer extends MinimalSerializer[ChannelConfig]({ +object ChannelConfigSerializer extends MinimalSerializer({ case x: ChannelConfig => JArray(x.options.toList.map(o => JString(o.name))) }) -object ChannelFeaturesSerializer extends MinimalSerializer[ChannelFeatures]({ +object ChannelFeaturesSerializer extends MinimalSerializer({ case channelFeatures: ChannelFeatures => JArray(channelFeatures.features.map(f => JString(f.rfcName)).toList) }) -object ChannelOpenResponseSerializer extends MinimalSerializer[ChannelOpenResponse]({ +object ChannelOpenResponseSerializer extends MinimalSerializer({ case x: ChannelOpenResponse => JString(x.toString) }) -object CommandResponseSerializer extends MinimalSerializer[CommandResponse[Command]]({ +object CommandResponseSerializer extends MinimalSerializer({ case RES_SUCCESS(_: CloseCommand, channelId) => JString(s"closed channel $channelId") case RES_FAILURE(_: Command, ex: Throwable) => JString(ex.getMessage) }) -object TransactionSerializer extends MinimalSerializer[TransactionWithInputInfo]({ +object TransactionSerializer extends MinimalSerializer({ case x: Transaction => JObject(List( JField("txid", JString(x.txid.toHex)), JField("tx", JString(x.toString())) )) }) -object TransactionWithInputInfoSerializer extends MinimalSerializer[TransactionWithInputInfo]({ +object TransactionWithInputInfoSerializer extends MinimalSerializer({ case x: HtlcSuccessTx => JObject(List( JField("txid", JString(x.tx.txid.toHex)), JField("tx", JString(x.tx.toString())), @@ -201,27 +191,27 @@ object TransactionWithInputInfoSerializer extends MinimalSerializer[TransactionW )) }) -object InetSocketAddressSerializer extends MinimalSerializer[InetSocketAddress]({ +object InetSocketAddressSerializer extends MinimalSerializer({ case address: InetSocketAddress => JString(HostAndPort.fromParts(address.getHostString, address.getPort).toString) }) -object OutPointSerializer extends MinimalSerializer[OutPoint]({ +object OutPointSerializer extends MinimalSerializer({ case x: OutPoint => JString(s"${x.txid}:${x.index}") }) -object OutPointKeySerializer extends MinimalKeySerializer[OutPoint]({ +object OutPointKeySerializer extends MinimalKeySerializer({ case x: OutPoint => s"${x.txid}:${x.index}" }) -object InputInfoSerializer extends MinimalSerializer[InputInfo]({ +object InputInfoSerializer extends MinimalSerializer({ case x: InputInfo => JObject(("outPoint", JString(s"${x.outPoint.txid}:${x.outPoint.index}")), ("amountSatoshis", JInt(x.txOut.amount.toLong))) }) -object ColorSerializer extends MinimalSerializer[Color]({ +object ColorSerializer extends MinimalSerializer({ case c: Color => JString(c.toString) }) -object RouteResponseSerializer extends MinimalSerializer[RouteResponse]({ +object RouteResponseSerializer extends MinimalSerializer({ case route: RouteResponse => val nodeIds = route.routes.head.hops match { case rest :+ last => rest.map(_.nodeId) :+ last.nodeId :+ last.nextNodeId @@ -230,24 +220,24 @@ object RouteResponseSerializer extends MinimalSerializer[RouteResponse]({ JArray(nodeIds.toList.map(n => JString(n.toString))) }) -object ThrowableSerializer extends MinimalSerializer[Throwable]({ +object ThrowableSerializer extends MinimalSerializer({ case t: Throwable if t.getMessage != null => JString(t.getMessage) case t: Throwable => JString(t.getClass.getSimpleName) }) -object FailureMessageSerializer extends MinimalSerializer[FailureMessage]({ +object FailureMessageSerializer extends MinimalSerializer({ case m: FailureMessage => JString(m.message) }) -object FailureTypeSerializer extends MinimalSerializer[FailureType]({ +object FailureTypeSerializer extends MinimalSerializer({ case ft: FailureType => JString(ft.toString) }) -object NodeAddressSerializer extends MinimalSerializer[NodeAddress]({ +object NodeAddressSerializer extends MinimalSerializer({ case n: NodeAddress => JString(HostAndPort.fromParts(n.socketAddress.getHostString, n.socketAddress.getPort).toString) }) -object DirectedHtlcSerializer extends MinimalSerializer[DirectedHtlc]({ +object DirectedHtlcSerializer extends MinimalSerializer({ case h: DirectedHtlc => new JObject(List(("direction", JString(h.direction)), ("add", Extraction.decompose(h.add)( DefaultFormats + ByteVector32Serializer + @@ -257,7 +247,7 @@ object DirectedHtlcSerializer extends MinimalSerializer[DirectedHtlc]({ CltvExpirySerializer)))) }) -object PaymentRequestSerializer extends MinimalSerializer[PaymentRequest]({ +object PaymentRequestSerializer extends MinimalSerializer({ case p: PaymentRequest => val expiry = p.expiry.map(ex => JField("expiry", JLong(ex))).toSeq val minFinalCltvExpiry = p.minFinalCltvExpiryDelta.map(mfce => JField("minFinalCltvExpiry", JInt(mfce.toInt))).toSeq @@ -288,15 +278,15 @@ object PaymentRequestSerializer extends MinimalSerializer[PaymentRequest]({ JObject(fieldList) }) -object FeaturesSerializer extends MinimalSerializer[Features]({ +object FeaturesSerializer extends MinimalSerializer({ case features: Features => JsonSerializers.featuresToJson(features) }) -object JavaUUIDSerializer extends MinimalSerializer[UUID]({ +object JavaUUIDSerializer extends MinimalSerializer({ case id: UUID => JString(id.toString) }) -object ChannelEventSerializer extends MinimalSerializer[ChannelEvent]({ +object ChannelEventSerializer extends MinimalSerializer({ case e: ChannelCreated => JObject( JField("type", JString("channel-opened")), JField("remoteNodeId", JString(e.remoteNodeId.toString())), @@ -319,7 +309,7 @@ object ChannelEventSerializer extends MinimalSerializer[ChannelEvent]({ ) }) -object OriginSerializer extends MinimalSerializer[Origin]({ +object OriginSerializer extends MinimalSerializer({ case o: Origin.Local => JObject(JField("paymentId", JString(o.id.toString))) case o: Origin.ChannelRelayed => JObject( JField("channelId", JString(o.originChannelId.toHex)), @@ -333,7 +323,7 @@ object OriginSerializer extends MinimalSerializer[Origin]({ }) }) -object GlobalBalanceSerializer extends MinimalSerializer[GlobalBalance]({ +object GlobalBalanceSerializer extends MinimalSerializer({ case o: GlobalBalance => val formats = DefaultFormats + ByteVector32KeySerializer + BtcSerializer + SatoshiSerializer JObject(JField("total", JDecimal(o.total.toDouble))) merge Extraction.decompose(o)(formats) From 505d418faa814120778235ca503eefbcf1e8908b Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 1 Oct 2021 14:27:44 +0200 Subject: [PATCH 5/7] new generic json serializer Instead of providing a `MyClass => JValue` conversion method, we provide a `MyClass => MyClassJson` method, with the assumption that `MyClassJson` is serializable using the base serializers. The rationale is that it's easier to define the structure with types rather than by building json objects. This also means that the serialization of attributes of class C is out of the scope when defining the serializer for class C. See for example how `DirectedHtlcSerializer` doesn't need anymore to bring in lower level serializers. It also has the advantage of removing recursion from custom serializers which sometimes generated weird stack overflows. --- .../acinq/eclair/json/JsonSerializers.scala | 49 ++++++++++++++----- .../eclair/json/JsonSerializersSpec.scala | 4 +- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index 30ff17b468..43c9bbe341 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -60,6 +60,33 @@ class MinimalKeySerializer(ser: PartialFunction[Any, String]) extends KeySeriali def serialize(implicit format: Formats): PartialFunction[Any, String] = ser } +/** + * Custom serializer where, instead of providing a `MyClass => JValue` conversion method, we provide a + * `MyClass => MyClassJson` method, with the assumption that `MyClassJson` is serializable using the base serializers. + * + * The rationale is that it's easier to define the structure with types rather than by building json objects. + * + * Usage: + * {{{ + * /** A type used in eclair */ + * case class Foo(a: String, b: Int, c: ByteVector32) + * + * /** Special purpose type used only for serialization */ + * private[json] case class FooJson(a: String, c: ByteVector32) + * object FooSerializer extends ConvertClassSerializer[Foo]({ foo: Foo => + * FooJson(foo.a, foo.c) + * }}} + * + */ +class ConvertClassSerializer[T: Manifest](f: T => Any) extends Serializer[Nothing] { + + def deserialize(implicit format: Formats): PartialFunction[(json4s.TypeInfo, JValue), Nothing] = PartialFunction.empty + + def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { + case o: T => Extraction.decompose(f(o)) + } +} + object ByteVectorSerializer extends MinimalSerializer({ case x: ByteVector => JString(x.toHex) }) @@ -203,9 +230,12 @@ object OutPointKeySerializer extends MinimalKeySerializer({ case x: OutPoint => s"${x.txid}:${x.index}" }) -object InputInfoSerializer extends MinimalSerializer({ - case x: InputInfo => JObject(("outPoint", JString(s"${x.outPoint.txid}:${x.outPoint.index}")), ("amountSatoshis", JInt(x.txOut.amount.toLong))) -}) +// @formatter:off +private case class InputInfoJson(outPoint: String, amountSatoshis: Long) +object InputInfoSerializer extends ConvertClassSerializer[InputInfo](i => + InputInfoJson(outPoint = s"${i.outPoint.txid}:${i.outPoint.index}", amountSatoshis = i.txOut.amount.toLong) +) +// @formatter:on object ColorSerializer extends MinimalSerializer({ case c: Color => JString(c.toString) @@ -237,15 +267,10 @@ object NodeAddressSerializer extends MinimalSerializer({ case n: NodeAddress => JString(HostAndPort.fromParts(n.socketAddress.getHostString, n.socketAddress.getPort).toString) }) -object DirectedHtlcSerializer extends MinimalSerializer({ - case h: DirectedHtlc => new JObject(List(("direction", JString(h.direction)), ("add", Extraction.decompose(h.add)( - DefaultFormats + - ByteVector32Serializer + - ByteVectorSerializer + - PublicKeySerializer + - MilliSatoshiSerializer + - CltvExpirySerializer)))) -}) +// @formatter:off +private case class DirectedHtlcJson(direction: String, add: UpdateAddHtlc) +object DirectedHtlcSerializer extends ConvertClassSerializer[DirectedHtlc](h => DirectedHtlcJson(direction = h.direction, add = h.add)) +// @formatter:on object PaymentRequestSerializer extends MinimalSerializer({ case p: PaymentRequest => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index 5d23fde61d..6e95825317 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -114,8 +114,8 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { val expectedIn = """{"direction":"IN","add":{"channelId":"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f","id":926,"amountMsat":12365,"paymentHash":"9fcd45bbaa09c60c991ac0425704163c3f3d2d683c789fa409455b9c97792692","cltvExpiry":621500,"onionRoutingPacket":{"version":0,"publicKey":"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b","payload":"3c7a66997c681a3de1bae56438abeee4fc50a16554725a430ade1dc8db6bdd76704d45c6151c4051d710cf487e63f8cbe9f5e537e6e518d7998f11c40277d551bee036d227a421f344c0185b4533660d200acbc4f4aa6148e29f813bb537e950a79ba961b80ccaa6ad808cb88f858ee73f8b1f129d3214d194f76fc011c46e18c2caec0fcb69715e79cb381e449e5be20281e0aaa92defa089c98b7e29c22181f2d1af9f5fe2a37ede4ac3163b123aa72318201b1128b17053c381e7bf111620dfb7ea3dcc5c28cafdd9cb7bb1a4202b64199aa02af145c563ba1b9f10288203ce2666f486aacb2ee385dc0b67915864c95174dfaac1e0ea195329d1741cd1febb4b49b33f84e0d10a5ec8ee0a1a94f73abf081ec69bb863edfeb46d24ce424b025ac3f593a7ba9419ec9b17fb39f0fbeac80e0898f95b6709cbc95f7c097e22e3a6ca0efbc947bbcd4a9077f6bd9daba25b18bb16179fca9d9cb2ce49fc7cbd2de589237926cacb87ea60e2cc60a90b47575517921b5529b8a95823dd0c3d02a7747d74c4ca927ba6b70c06c1c1ef27e14d371e8dd8f5d9380a65b08ae1e6384f9b3575c5d7278de22ce80e63612a27f3b3f45dbe32ee855185293c719e5a7203a682a08fd810c46fa12b67e61349831f8fae3f558090ea988e1a22ec877b790ea09169055529247c4dd597857aad74eaeb3a5879e96453e681e213f2796ed704d620509f34f91d9d16f881fd397e2836a0a4d2f1bcd230067f7acb5381a2b17e8c5135e38c4d258afbe4f69ac7ad39b789e99686ee926b3ad31b98993673313b7b18a4faaea238d8055824fde7339a791fc7777ef28cc4a1a5d177b3c3882ced4921c6cd85ae82e1fe13fe680ae432a9918ce37b15f88d4d18fb16b69e5369d18c204aaf7ee49b830bf2328380e6ad96e8f6a9e01bc2c97ffbaa402d5406dc7b83c6eb5d515ffc3bea8c42cf299c9e2bea693515246c6ff859d33ba6c4da4c4c1706e0c6b4a574e927f02eb92b7d56722cff80c3e6f6b98d1c84cb576abdcc27a6bc7b110fc2ac5fead57f05ad854c3331ce1ff94c0dc97303540ee797d71566497af09f20e3554d467528e1fed8e69438171072fe2deca3979a8f5ec9043b9bc4da921b095c29dc0294148c1b7001dafda4c48600d1194f745e6d0689c561bf19d20758c4d25fac64d81780607a4106e220ef546fc4026af7b9da8defb2fe3c21d48798ac67c794fb40aabe44618a8911673466be06808c6f54a772b87bcfafb4d120a9bebffb8051bf24bb332eaa769cf175c1aadb0186f8946dc32513fd81fe1a61bfb860886bdd070359a43e06e74607d300bd2e01a3f1ee900c4039e8db742170228db61ef0c77724c49d1573144564a80cc1ebc0449b34f84be35187ceba3fbc2facf5ad1f1e15945e3c6c236579aca7bc97e4cc76a3310022693b64008562b254a7d11c0086813e551c4817bbb72a1d6fbfc84d326ce973651200f80aa8ab0976c53c390249ca8e7e5ec21b80e70c3e0205983d313b28a5d1b9d9149501e05d3257c8ae88c6308e9e00feeab19121d1032a582e68ca1f9f64a1fd91cb5d8613b985fd4be22a4d5c14a132c20811a75ee3cc61de0b3fbc3254d61995d086603032269888b942ec0971ad26ea4b8df1746c5ec1de904ddeb5045abc0a6ede9d6a199ed0782cb69efa3a4dc00747553dbef12fb8299ca364b4cdf2ac3eba03b0d8b273684116bba4458b5717bc4aca5406901173a89b3643ccc076f22779fccf1ad69981e24eef18c711a2d58dfe5834b41f9e7166d54dc8628e754baaca1cbb7db8256f88ebc889de6078ba83a1af14a4","hmac":"9442626f72c475963dbddf8a57ab2cef3013eb3d6a5e8afbea9e631dac4481f5"},"tlvStream":{"records":[],"unknown":[]}}}""" - JsonSerializers.serialization.write(IncomingHtlc(add))(org.json4s.DefaultFormats + DirectedHtlcSerializer) shouldBe expectedIn - JsonSerializers.serialization.write(OutgoingHtlc(add))(org.json4s.DefaultFormats + DirectedHtlcSerializer) shouldBe expectedIn.replace("IN", "OUT") + JsonSerializers.serialization.write(IncomingHtlc(add))(JsonSerializers.formats) shouldBe expectedIn + JsonSerializers.serialization.write(OutgoingHtlc(add))(JsonSerializers.formats) shouldBe expectedIn.replace("IN", "OUT") } test("HTLC origin serialization") { From 62c9e41510694d8b8d617eb3c46ad68d4b033e29 Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 1 Oct 2021 15:59:12 +0200 Subject: [PATCH 6/7] fixup! new generic json serializer --- .../scala/fr/acinq/eclair/json/JsonSerializers.scala | 6 ++---- .../fr/acinq/eclair/json/JsonSerializersSpec.scala | 11 ++++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index 43c9bbe341..d761b367b5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -231,10 +231,8 @@ object OutPointKeySerializer extends MinimalKeySerializer({ }) // @formatter:off -private case class InputInfoJson(outPoint: String, amountSatoshis: Long) -object InputInfoSerializer extends ConvertClassSerializer[InputInfo](i => - InputInfoJson(outPoint = s"${i.outPoint.txid}:${i.outPoint.index}", amountSatoshis = i.txOut.amount.toLong) -) +private case class InputInfoJson(outPoint: OutPoint, amountSatoshis: Satoshi) +object InputInfoSerializer extends ConvertClassSerializer[InputInfo](i => InputInfoJson(i.outPoint, i.txOut.amount)) // @formatter:on object ColorSerializer extends MinimalSerializer({ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index 6e95825317..40a68871ff 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -16,7 +16,7 @@ package fr.acinq.eclair.json -import fr.acinq.bitcoin.{Btc, ByteVector32, OutPoint, Satoshi, Transaction, TxOut} +import fr.acinq.bitcoin.{Btc, ByteVector32, OutPoint, Satoshi, SatoshiLong, Transaction, TxOut} import fr.acinq.eclair._ import fr.acinq.eclair.balance.CheckBalance import fr.acinq.eclair.balance.CheckBalance.{ClosingBalance, GlobalBalance, MainAndHtlcBalance, PossiblyPublishedMainAndHtlcBalance, PossiblyPublishedMainBalance} @@ -132,6 +132,15 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { JsonSerializers.serialization.write(trampolineOrigin)(org.json4s.DefaultFormats + OriginSerializer) shouldBe expectedTrampolineOrigin } + test("InputInfo serialization") { + val inputInfo = InputInfo( + outPoint = OutPoint(ByteVector32(hex"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f"), 42), + txOut = TxOut(456651 sat, hex"3c7a66997c681a3de1bae56438abeee4fc50a16554725a430ade1dc8db6bdd76704d45c6151c4051d710cf487e63"), + redeemScript = hex"00dc6c50f445ed53d2fb41067fdcb25686fe79492d90e6e5db43235726ace247210220773" + ) + JsonSerializers.serialization.write(inputInfo)(JsonSerializers.formats) shouldBe """{"outPoint":"9f0b9c0ce92c175ca4e78acfd13a718099c73818b6d3140cfe6f04ec052b5b34:42","amountSatoshis":456651}""" + } + test("Payment Request") { val ref = "lnbcrt50n1p0fm9cdpp5al3wvsfkc6p7fxy89eu8gm4aww9mseu9syrcqtpa4mvx42qelkwqdq9v9ekgxqrrss9qypqsqsp5wl2t45v0hj4lgud0zjxcnjccd29ts0p2kh4vpw75vnhyyzyjtjtqarpvqg33asgh3z5ghfuvhvtf39xtnu9e7aqczpgxa9quwsxkd9rnwmx06pve9awgeewxqh90dqgrhzgsqc09ek6uejr93z8puafm6gsqgrk0hy" val pr = PaymentRequest.read(ref) From 601fff1747debe70ae3108c69fc0c8a84c714268 Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 1 Oct 2021 18:37:41 +0200 Subject: [PATCH 7/7] simplify features serializer --- .../acinq/eclair/json/JsonSerializers.scala | 33 ++++++++++--------- .../eclair/json/JsonSerializersSpec.scala | 16 +++++++++ .../internal/channel/ChannelCodecsSpec.scala | 2 +- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index d761b367b5..81cefaca60 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -30,7 +30,7 @@ import fr.acinq.eclair.router.Router.RouteResponse import fr.acinq.eclair.transactions.DirectedHtlc import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, ShortChannelId, UInt64} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, FeatureSupport, MilliSatoshi, ShortChannelId, UInt64, UnknownFeature} import org.json4s import org.json4s.JsonAST._ import org.json4s.jackson.Serialization @@ -156,6 +156,12 @@ object PrivateKeySerializer extends MinimalSerializer({ case _: PrivateKey => JString("XXX") }) +object FeatureKeySerializer extends MinimalKeySerializer({ case f: Feature => f.rfcName }) + +object FeatureSupportSerializer extends MinimalSerializer({ case s: FeatureSupport => JString(s.toString) }) + +object UnknownFeatureSerializer extends MinimalSerializer({ case f: UnknownFeature => JInt(f.bitIndex) }) + object ChannelConfigSerializer extends MinimalSerializer({ case x: ChannelConfig => JArray(x.options.toList.map(o => JString(o.name))) }) @@ -275,7 +281,12 @@ object PaymentRequestSerializer extends MinimalSerializer({ val expiry = p.expiry.map(ex => JField("expiry", JLong(ex))).toSeq val minFinalCltvExpiry = p.minFinalCltvExpiryDelta.map(mfce => JField("minFinalCltvExpiry", JInt(mfce.toInt))).toSeq val amount = p.amount.map(msat => JField("amount", JLong(msat.toLong))).toSeq - val features = JField("features", JsonSerializers.featuresToJson(Features(p.features.bitmask))) + val features = JField("features", Extraction.decompose(p.features.features)( + DefaultFormats + + FeatureKeySerializer + + FeatureSupportSerializer + + UnknownFeatureSerializer + )) val routingInfo = JField("routingInfo", Extraction.decompose(p.routingInfo)( DefaultFormats + ByteVector32Serializer + @@ -284,8 +295,7 @@ object PaymentRequestSerializer extends MinimalSerializer({ ShortChannelIdSerializer + MilliSatoshiSerializer + CltvExpiryDeltaSerializer - ) - ) + )) val fieldList = List(JField("prefix", JString(p.prefix)), JField("timestamp", JLong(p.timestamp)), JField("nodeId", JString(p.nodeId.toString())), @@ -301,10 +311,6 @@ object PaymentRequestSerializer extends MinimalSerializer({ JObject(fieldList) }) -object FeaturesSerializer extends MinimalSerializer({ - case features: Features => JsonSerializers.featuresToJson(features) -}) - object JavaUUIDSerializer extends MinimalSerializer({ case id: UUID => JString(id.toString) }) @@ -435,6 +441,9 @@ object JsonSerializers { InetSocketAddressSerializer + OutPointSerializer + OutPointKeySerializer + + FeatureKeySerializer + + FeatureSupportSerializer + + UnknownFeatureSerializer + ChannelConfigSerializer + ChannelFeaturesSerializer + ChannelOpenResponseSerializer + @@ -449,15 +458,7 @@ object JsonSerializers { DirectedHtlcSerializer + PaymentRequestSerializer + JavaUUIDSerializer + - FeaturesSerializer + OriginSerializer + GlobalBalanceSerializer - def featuresToJson(features: Features): JObject = JObject( - JField("activated", JObject(features.activated.map { case (feature, support) => - feature.rfcName -> JString(support.toString) - }.toList)), - JField("unknown", JArray(features.unknown.map(u => JInt(u.bitIndex)).toList)) - ) - } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index 40a68871ff..96c0fbb8a0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -141,6 +141,22 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { JsonSerializers.serialization.write(inputInfo)(JsonSerializers.formats) shouldBe """{"outPoint":"9f0b9c0ce92c175ca4e78acfd13a718099c73818b6d3140cfe6f04ec052b5b34:42","amountSatoshis":456651}""" } + test("Features serialization") { + val features = Features( + activated = Map( + Features.InitialRoutingSync -> FeatureSupport.Optional, + Features.PaymentSecret -> FeatureSupport.Mandatory, + Features.StaticRemoteKey -> FeatureSupport.Optional + ), + unknown = Set( + UnknownFeature(457), + UnknownFeature(5000), + ) + ) + + JsonSerializers.serialization.write(features)(JsonSerializers.formats) shouldBe """{"activated":{"initial_routing_sync":"optional","payment_secret":"mandatory","option_static_remotekey":"optional"},"unknown":[457,5000]}""" + } + test("Payment Request") { val ref = "lnbcrt50n1p0fm9cdpp5al3wvsfkc6p7fxy89eu8gm4aww9mseu9syrcqtpa4mvx42qelkwqdq9v9ekgxqrrss9qypqsqsp5wl2t45v0hj4lgud0zjxcnjccd29ts0p2kh4vpw75vnhyyzyjtjtqarpvqg33asgh3z5ghfuvhvtf39xtnu9e7aqczpgxa9quwsxkd9rnwmx06pve9awgeewxqh90dqgrhzgsqc09ek6uejr93z8puafm6gsqgrk0hy" val pr = PaymentRequest.read(ref) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala index 6c1f78c742..cf8fa14aa6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala @@ -131,7 +131,7 @@ class ChannelCodecsSpec extends AnyFunSuite { hex"00000303933884AAF1D6B108397E5EFE5C86BCF2D8CA8D2F700EDA99DB9214FC2712B1340004D443ECE9D9C43A11A19B554BAAA6AD150000000000000222000000003B9ACA0000000000000249F000000000000000010090001E800BD48A22F4C80A42CC8BB29A764DBAEFC95674931FBE9A4380000000C50134D4A745996002F219B5FDBA1E045374DF589ECA06ABE23CECAE47343E65EDCF800000000000011E80000001BA90824000000000000124F800000000000001F4038500F1810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E2266201E8BFEEEEED725775B8116F6F82CF8E87835A5B45B184E56F272AD70D6078118601E06212B8C8F2E25B73EE7974FDCDF007E389B437BBFE238CCC3F3BF7121B6C5E81AA8589D21E9584B24A11F3ABBA5DAD48D121DD63C57A69CD767119C05DA159CB81A649D8CC0E136EB8DFBD2268B69DCA86F8CE4A604235A03D9D37AE7B07FC563F80000000C080800000000000271C000000000177000000002808B14600000001970039BA00123767F0F4F00D5E9FDF24177EF2872343D9F8FAEC65D3048BA575E70E00A0AB08800000000015E070F20000000000110010584241B5FB364208F6E64A80D1166DAD866186B10C015ED0283FF1C308C2105A0023A910810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E226621081DE8ADFA110DC8A94D8B9E9EF616BAE8598287C8F82AFDF0FC068697D570266FDA95700AD81000000000080B767F0F4F00D5E9FDF24177EF2872343D9F8FAEC65D3048BA575E70E00A0AB0880000000003E7AEDC0011ABE8A00000000001100101A9CE4B6AEF469590BC7BCC51DCEEAE9C86084055A63CC01E443C733FBE400B9B5B16800000000000B000A5E5700106D1A7097E4DE87EBAF1F8F2773842FA482002418228110805E84989A81F51ABD9D11889AE43E68FAD93659DEC019F1B8C0ADBF15A57B118B81101DCC1256F9306439AD3962C043FC47A5179CAAA001CCB23342BE0E8D92E4022780A4182281108074F306DA3751B84EC5FFB155BDCA7B8E02208BBDBC8D4F3327ABA557BF27CD1701102EF4AC8CC92F469DA9642D4D4162BC545F8B34ADE15B7D6F99808AA22B086B0180A3A910810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E226621081DE8ADFA110DC8A94D8B9E9EF616BAE8598287C8F82AFDF0FC068697D570266FDA9576F8099900000000000000000271C00000000017700000001970039BA000000002808B14648CE00AE97051EE10A3C361263F81A98165CE4AA7BA076933D4266E533585F24815C15DEACF0691332B38ECF23EC39982C5C978C748374A01BA9B30D501EE4F26E8000000000000000000000000000000000001224000000000000004B800040A911C460F1467952E3B99BED072F81BFB4454FF389636DCB399FE6A78113C28580091BB3F87A7806AF4FEF920BBF794391A1ECFC7D7632E98245D2BAF3870050558440000000000AF0387900000000000880082C2120DAFD9B21047B732540688B36D6C330C3588600AF68141FF8E18461082D0011D488408570D7C50EB7AB7C042AF13382F8C8DD83E6A7121A5E2DD8B4C73F2C407113310840EF456FD0886E454A6C5CF4F7B0B5D742CC143E47C157EF87E03434BEAB81337ED4AB8001C00F40003FFFFFFFEC7200403248A1D44DFA3AC9EC237D452C936400CAA86E9517CCCF2A8F77B7493CD70B6A00780001FFFFFFFF63A0041826829646B907A97FBD1455EA8673A12B8E7AA6EA790F7802E955CE3B69DE57E006E0001FFFFFFFF640081E51EB1F91218821E680B50E4B22DF8B094385BD33ACAE36BFC9E8C2F5AD2DA5400EC0003FFFFFFFEC7801047C26AD5435658D063EBCF73A5D0EEFE73ED6B73426246E8DFB3A21D1C4C7465001900007FFFFFFFE0040B115AC58BAAA900195893EA3B2AB408D2AD348AD047E3B6CB15E599625E38608006A0001FFFFFFFF7002033C39A21A38BB61F6FB33623771A9356D8885B7C12C939C770C939EF826286C200360000FFFFFFFFB4008104EF4271064A0973B053727C3E67352D00E25CAEED944F50782449CEAE8F50960001FFFFFFFF6390DD9FC3D3C0357A7F7C905DFBCA1C8D0F67E3EBB1974C122E95D79C380282AC222B21FA0007920001295AA1FB77029F7620A90EF7AE6A6CD31E4588B93264A7ADB76152D535C52E90B9E1B7C2376DABA316A6290F1A9730D4E5E44D0B1CB0EE6A795702E6A6BCDFCDA1A4BFEBFC134AB8847A5187ECE761D75D3CCB904274875680F51984800000000AC87E8001E480002E884D2A8080804800000000000001F4000001F40000003200000001BF08EB000" -> """{"type":"DATA_NORMAL","commitments":{"channelId":"6ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c01415611","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[3561221353,3653515793,2711311691,2863050005]},"dustLimit":546,"maxHtlcValueInFlightMsat":1000000000,"channelReserve":150000,"htlcMinimum":1,"toSelfDelay":144,"maxAcceptedHtlcs":30,"isFunder":true,"defaultFinalScriptPubKey":"a91445e990148599176534ec9b75df92ace9263f7d3487","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"0269a94e8b32c005e4336bfb743c08a6e9beb13d940d57c479d95c8e687ccbdb9f","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"channelReserve":150000,"htlcMinimum":1000,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"0215c35f143adeadf010abc4ce0be323760f9a9c486978b762d31cfcb101c44cc4","revocationBasepoint":"03d17fdddddae4aeeb7022dedf059f1d0f06b4b68b6309cade4e55ae1ac0f0230c","paymentBasepoint":"03c0c4257191e5c4b6e7dcf2e9fb9be00fc713686f77fc4719987e77ee2436d8bd","delayedPaymentBasepoint":"03550b13a43d2b09649423e75774bb5a91a243bac78af4d39aece23380bb42b397","htlcBasepoint":"034c93b1981c26dd71bf7a44d16d3b950df19c94c0846b407b3a6f5cf60ff8ac7f","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":1,"localCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":1343316620,"toRemote":13656683380},"commitTxAndRemoteSig":{"commitTx":{"txid":"65fe0b1f079fa763448df3ab8d94b1ad7d377c061121376be90b9c0c1bb0cd43","tx":"02000000016ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c0141561100000000007cf5db8002357d1400000000002200203539c96d5de8d2b2178f798a3b9dd5d390c1080ab4c79803c8878e67f7c801736b62d00000000000160014bcae0020da34e12fc9bd0fd75e3f1e4ee7085f49df013320"},"remoteSig":"bd09313503ea357b3a231135c87cd1f5b26cb3bd8033e371815b7e2b4af623173b9824adf260c8735a72c58087f88f4a2f39554003996466857c1d1b25c8044f"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":13656683380,"toRemote":1343316620},"txid":"919c015d2e0a3dc214786c24c7f035302cb9c954f740ed267a84cdca66b0be49","remotePerCommitmentPoint":"02b82bbd59e0d22665671d9e47d8733058b92f18e906e9403753661aa03dc9e4dd"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":9288,"remoteNextHtlcId":151,"originChannels":{},"remoteNextCommitInfo":"02a4471183c519e54b8ee66fb41cbe06fed1153fce258db72ce67f9a9e044f0a16","commitInput":{"outPoint":"115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortChannelId":"1413373x969x0","buried":true,"channelUpdate":{"signature":"52b543f6ee053eec41521def5cd4d9a63c8b117264c94f5b6ec2a5aa6b8a5d2173c36f846edb57462d4c521e352e61a9cbc89a163961dcd4f2ae05cd4d79bf9b","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1413373x969x0","timestamp":1561369173,"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""", hex"0200020000000303933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13400098c4b989bbdced820a77a7186c2320e7d176a5c8b5c16d6ac2af3889d6bc8bf8080000001000000000000022200000004a817c80000000000000249f0000000000000000102d0001eff1600148061b7fbd2d84ed1884177ea785faecb2080b10302e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b300000004080aa982027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8000000000000023d000000037521048000000000000249f00000000000000001070a01e302eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b7503c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a5700000004808a52a1010000000000000004000000001046000000037e11d6000000000000000000245986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b000000002bc0e1e40000000000220020690fb50de412adf9b20a7fc6c8fb86f1bfd4ebc1ef8e2d96a5a196560798d944475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52aefd013b020000000001015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61040047304402207f8c1936d0a50671c993890f887c78c6019abc2a2e8018899dcdc0e891fd2b090220046b56afa2cb7e9470073c238654ecf584bcf5c00b96b91e38335a70e2739ec901483045022100871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c0220119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b01475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52aed7782c20000000000000000000040000000010460000000000000000000000037e11d600b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d802e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a000000000000000000000000000000000000000000000000000000000000ff03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d245986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b000000002bc0e1e40000000000220020690fb50de412adf9b20a7fc6c8fb86f1bfd4ebc1ef8e2d96a5a196560798d944475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52ae0001003e0000fffffffffffc0080474b8cf7bb98217dd8dc475cb7c057a3465d466728978bbb909d0a05d4ae7bbe0001fffffffffff85986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b1eedce0000010000fffffd01ae98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be54920134196992f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef09bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000001eedce0000010000027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b803933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13402eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d88710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000001eedce000001000060e6eb14010100900000000000000001000003e800000064000000037e11d6000000" - -> """{"type":"DATA_NORMAL","commitments":{"channelId":"5986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b","channelConfig":["funding_pubkey_based_channel_keypath"],"channelFeatures":["option_static_remotekey"],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[2353764507,3184449568,2809819526,3258060413,392846475,1545000620,720603293,1808318336,2147483649]},"dustLimit":546,"maxHtlcValueInFlightMsat":20000000000,"channelReserve":150000,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isFunder":true,"defaultFinalScriptPubKey":"00148061b7fbd2d84ed1884177ea785faecb2080b103","walletStaticPaymentBasepoint":"02e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b3","initFeatures":{"activated":{"gossip_queries":"optional","option_shutdown_anysegwit":"optional","payment_secret":"optional","option_data_loss_protect":"optional","option_static_remotekey":"optional","basic_mpp":"optional","gossip_queries_ex":"optional","option_support_large_channel":"optional","var_onion_optin":"mandatory"},"unknown":[]}},"remoteParams":{"nodeId":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"channelReserve":150000,"htlcMinimum":1,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","revocationBasepoint":"0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b75","paymentBasepoint":"03c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd","delayedPaymentBasepoint":"03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8","htlcBasepoint":"022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a57","initFeatures":{"activated":{"gossip_queries":"optional","basic_mpp":"optional","payment_secret":"mandatory","option_data_loss_protect":"mandatory","option_static_remotekey":"mandatory","option_anchors_zero_fee_htlc_tx":"optional","option_upfront_shutdown_script":"optional","option_support_large_channel":"optional","var_onion_optin":"optional"},"unknown":[31]}},"channelFlags":1,"localCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":15000000000,"toRemote":0},"commitTxAndRemoteSig":{"commitTx":{"txid":"fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f","tx":"02000000015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61d7782c20"},"remoteSig":"871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":0,"toRemote":15000000000},"txid":"b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d8","remotePerCommitmentPoint":"02e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":0,"remoteNextHtlcId":0,"originChannels":{},"remoteNextCommitInfo":"03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d","commitInput":{"outPoint":"1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortChannelId":"2026958x1x0","buried":true,"channelAnnouncement":{"nodeSignature1":"98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be549201341969","nodeSignature2":"92f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef0","bitcoinSignature1":"9bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f","bitcoinSignature2":"84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","nodeId1":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","bitcoinKey2":"023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","timestamp":1625746196,"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""" + -> """{"type":"DATA_NORMAL","commitments":{"channelId":"5986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b","channelConfig":["funding_pubkey_based_channel_keypath"],"channelFeatures":["option_static_remotekey"],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[2353764507,3184449568,2809819526,3258060413,392846475,1545000620,720603293,1808318336,2147483649]},"dustLimit":546,"maxHtlcValueInFlightMsat":20000000000,"channelReserve":150000,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isFunder":true,"defaultFinalScriptPubKey":"00148061b7fbd2d84ed1884177ea785faecb2080b103","walletStaticPaymentBasepoint":"02e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b3","initFeatures":{"activated":{"option_support_large_channel":"optional","gossip_queries_ex":"optional","option_data_loss_protect":"optional","var_onion_optin":"mandatory","option_static_remotekey":"optional","payment_secret":"optional","option_shutdown_anysegwit":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"channelReserve":150000,"htlcMinimum":1,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","revocationBasepoint":"0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b75","paymentBasepoint":"03c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd","delayedPaymentBasepoint":"03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8","htlcBasepoint":"022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a57","initFeatures":{"activated":{"option_upfront_shutdown_script":"optional","payment_secret":"mandatory","option_data_loss_protect":"mandatory","var_onion_optin":"optional","option_static_remotekey":"mandatory","option_support_large_channel":"optional","option_anchors_zero_fee_htlc_tx":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[31]}},"channelFlags":1,"localCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":15000000000,"toRemote":0},"commitTxAndRemoteSig":{"commitTx":{"txid":"fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f","tx":"02000000015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61d7782c20"},"remoteSig":"871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":0,"toRemote":15000000000},"txid":"b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d8","remotePerCommitmentPoint":"02e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":0,"remoteNextHtlcId":0,"originChannels":{},"remoteNextCommitInfo":"03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d","commitInput":{"outPoint":"1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortChannelId":"2026958x1x0","buried":true,"channelAnnouncement":{"nodeSignature1":"98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be549201341969","nodeSignature2":"92f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef0","bitcoinSignature1":"9bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f","bitcoinSignature2":"84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","nodeId1":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","bitcoinKey2":"023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","timestamp":1625746196,"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""" ) refs.foreach { case (oldbin, refjson) =>