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..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 @@ -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._ @@ -31,139 +30,163 @@ 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 -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 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(ser: PartialFunction[Any, JValue]) extends Serializer[Nothing] { - val Class: Class[_] = implicitly[Manifest[A]].runtimeClass + def deserialize(implicit format: Formats): PartialFunction[(json4s.TypeInfo, JValue), Nothing] = PartialFunction.empty - def deserialize(implicit format: Formats): PartialFunction[(json4s.TypeInfo, JValue), A] = { - 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(ser: PartialFunction[Any, String]) extends KeySerializer[Nothing] { - val Class = implicitly[Manifest[A]].runtimeClass + def deserialize(implicit format: Formats): PartialFunction[(json4s.TypeInfo, String), Nothing] = PartialFunction.empty - 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 +} - def serialize(implicit format: Formats): PartialFunction[Any, String] = ser(format) +/** + * 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)) + } } -class ByteVectorSerializer extends CustomSerializerOnly[ByteVector](_ => { +object ByteVectorSerializer extends MinimalSerializer({ case x: ByteVector => JString(x.toHex) }) -class ByteVector32Serializer extends CustomSerializerOnly[ByteVector32](_ => { +object ByteVector32Serializer extends MinimalSerializer({ case x: ByteVector32 => JString(x.toHex) }) -class ByteVector32KeySerializer extends CustomKeySerializerOnly[ByteVector32](_ => { +object ByteVector32KeySerializer extends MinimalKeySerializer({ case x: ByteVector32 => x.toHex }) -class ByteVector64Serializer extends CustomSerializerOnly[ByteVector64](_ => { +object ByteVector64Serializer extends MinimalSerializer({ case x: ByteVector64 => JString(x.toHex) }) -class UInt64Serializer extends CustomSerializerOnly[UInt64](_ => { +object UInt64Serializer extends MinimalSerializer({ case x: UInt64 => JInt(x.toBigInt) }) -class BtcSerializer extends CustomSerializerOnly[Btc](_ => { +object BtcSerializer extends MinimalSerializer({ case x: Btc => JDecimal(x.toDouble) }) -class SatoshiSerializer extends CustomSerializerOnly[Satoshi](_ => { +object SatoshiSerializer extends MinimalSerializer({ case x: Satoshi => JInt(x.toLong) }) -class MilliSatoshiSerializer extends CustomSerializerOnly[MilliSatoshi](_ => { +object MilliSatoshiSerializer extends MinimalSerializer({ case x: MilliSatoshi => JInt(x.toLong) }) -class CltvExpirySerializer extends CustomSerializerOnly[CltvExpiry](_ => { +object CltvExpirySerializer extends MinimalSerializer({ case x: CltvExpiry => JLong(x.toLong) }) -class CltvExpiryDeltaSerializer extends CustomSerializerOnly[CltvExpiryDelta](_ => { +object CltvExpiryDeltaSerializer extends MinimalSerializer({ case x: CltvExpiryDelta => JInt(x.toInt) }) -class FeeratePerKwSerializer extends CustomSerializerOnly[FeeratePerKw](_ => { +object FeeratePerKwSerializer extends MinimalSerializer({ case x: FeeratePerKw => JLong(x.toLong) }) -class ShortChannelIdSerializer extends CustomSerializerOnly[ShortChannelId](_ => { +object ShortChannelIdSerializer extends MinimalSerializer({ case x: ShortChannelId => JString(x.toString) }) -class ChannelIdentifierSerializer extends CustomKeySerializerOnly[ChannelIdentifier](_ => { +object ChannelIdentifierSerializer extends MinimalKeySerializer({ case Left(x: ByteVector32) => x.toHex case Right(x: ShortChannelId) => x.toString }) -class ChannelStateSerializer extends CustomSerializerOnly[ChannelState](_ => { +object ChannelStateSerializer extends MinimalSerializer({ case x: ChannelState => JString(x.toString) }) -class ShaChainSerializer extends CustomSerializerOnly[ShaChain](_ => { +object ShaChainSerializer extends MinimalSerializer({ case _: ShaChain => JNull }) -class PublicKeySerializer extends CustomSerializerOnly[PublicKey](_ => { +object PublicKeySerializer extends MinimalSerializer({ case x: PublicKey => JString(x.toString()) }) -class PrivateKeySerializer extends CustomSerializerOnly[PrivateKey](_ => { +object PrivateKeySerializer extends MinimalSerializer({ case _: PrivateKey => JString("XXX") }) -class ChannelConfigSerializer extends CustomSerializerOnly[ChannelConfig](_ => { +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))) }) -class ChannelFeaturesSerializer extends CustomSerializerOnly[ChannelFeatures](_ => { +object ChannelFeaturesSerializer extends MinimalSerializer({ case channelFeatures: ChannelFeatures => JArray(channelFeatures.features.map(f => JString(f.rfcName)).toList) }) -class ChannelOpenResponseSerializer extends CustomSerializerOnly[ChannelOpenResponse](_ => { +object ChannelOpenResponseSerializer extends MinimalSerializer({ case x: ChannelOpenResponse => JString(x.toString) }) -class CommandResponseSerializer extends CustomSerializerOnly[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) }) -class TransactionSerializer extends CustomSerializerOnly[TransactionWithInputInfo](_ => { +object TransactionSerializer extends MinimalSerializer({ case x: Transaction => JObject(List( JField("txid", JString(x.txid.toHex)), JField("tx", JString(x.toString())) )) }) -class TransactionWithInputInfoSerializer extends CustomSerializerOnly[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 +224,28 @@ class TransactionWithInputInfoSerializer extends CustomSerializerOnly[Transactio )) }) -class InetSocketAddressSerializer extends CustomSerializerOnly[InetSocketAddress](_ => { +object InetSocketAddressSerializer extends MinimalSerializer({ case address: InetSocketAddress => JString(HostAndPort.fromParts(address.getHostString, address.getPort).toString) }) -class OutPointSerializer extends CustomSerializerOnly[OutPoint](_ => { +object OutPointSerializer extends MinimalSerializer({ case x: OutPoint => JString(s"${x.txid}:${x.index}") }) -class OutPointKeySerializer extends CustomKeySerializerOnly[OutPoint](_ => { +object OutPointKeySerializer extends MinimalKeySerializer({ case x: OutPoint => s"${x.txid}:${x.index}" }) -class InputInfoSerializer extends CustomSerializerOnly[InputInfo](_ => { - 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: OutPoint, amountSatoshis: Satoshi) +object InputInfoSerializer extends ConvertClassSerializer[InputInfo](i => InputInfoJson(i.outPoint, i.txOut.amount)) +// @formatter:on -class ColorSerializer extends CustomSerializerOnly[Color](_ => { +object ColorSerializer extends MinimalSerializer({ case c: Color => JString(c.toString) }) -class RouteResponseSerializer extends CustomSerializerOnly[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,49 +254,48 @@ class RouteResponseSerializer extends CustomSerializerOnly[RouteResponse](_ => { JArray(nodeIds.toList.map(n => JString(n.toString))) }) -class ThrowableSerializer extends CustomSerializerOnly[Throwable](_ => { +object ThrowableSerializer extends MinimalSerializer({ 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 MinimalSerializer({ case m: FailureMessage => JString(m.message) }) -class FailureTypeSerializer extends CustomSerializerOnly[FailureType](_ => { +object FailureTypeSerializer extends MinimalSerializer({ case ft: FailureType => JString(ft.toString) }) -class NodeAddressSerializer extends CustomSerializerOnly[NodeAddress](_ => { +object NodeAddressSerializer extends MinimalSerializer({ case n: NodeAddress => JString(HostAndPort.fromParts(n.socketAddress.getHostString, n.socketAddress.getPort).toString) }) -class 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)))) -}) +// @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 -class PaymentRequestSerializer extends CustomSerializerOnly[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 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 + - 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)), JField("timestamp", JLong(p.timestamp)), JField("nodeId", JString(p.nodeId.toString())), @@ -288,15 +311,11 @@ class PaymentRequestSerializer extends CustomSerializerOnly[PaymentRequest](_ => JObject(fieldList) }) -class FeaturesSerializer extends CustomSerializerOnly[Features](_ => { - case features: Features => JsonSerializers.featuresToJson(features) -}) - -class JavaUUIDSerializer extends CustomSerializerOnly[UUID](_ => { +object JavaUUIDSerializer extends MinimalSerializer({ case id: UUID => JString(id.toString) }) -class ChannelEventSerializer extends CustomSerializerOnly[ChannelEvent](_ => { +object ChannelEventSerializer extends MinimalSerializer({ case e: ChannelCreated => JObject( JField("type", JString("channel-opened")), JField("remoteNodeId", JString(e.remoteNodeId.toString())), @@ -319,7 +338,7 @@ class ChannelEventSerializer extends CustomSerializerOnly[ChannelEvent](_ => { ) }) -class OriginSerializer extends CustomSerializerOnly[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,9 +352,9 @@ class OriginSerializer extends CustomSerializerOnly[Origin](_ => { }) }) -class GlobalBalanceSerializer extends CustomSerializerOnly[GlobalBalance](_ => { +object GlobalBalanceSerializer extends MinimalSerializer({ 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) }) @@ -395,56 +414,51 @@ 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 + + implicit val formats: Formats = org.json4s.DefaultFormats.withTypeHintFieldName("type") + CustomTypeHints.incomingPaymentStatus + CustomTypeHints.outgoingPaymentStatus + CustomTypeHints.paymentEvent + - CustomTypeHints.channelStates).withTypeHintFieldName("type") - - 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)) - ) + CustomTypeHints.channelStates + + ByteVectorSerializer + + ByteVector32Serializer + + ByteVector64Serializer + + ChannelEventSerializer + + UInt64Serializer + + BtcSerializer + + SatoshiSerializer + + MilliSatoshiSerializer + + CltvExpirySerializer + + CltvExpiryDeltaSerializer + + FeeratePerKwSerializer + + ShortChannelIdSerializer + + ChannelIdentifierSerializer + + ChannelStateSerializer + + ShaChainSerializer + + PublicKeySerializer + + PrivateKeySerializer + + TransactionSerializer + + TransactionWithInputInfoSerializer + + InetSocketAddressSerializer + + OutPointSerializer + + OutPointKeySerializer + + FeatureKeySerializer + + FeatureSupportSerializer + + UnknownFeatureSerializer + + ChannelConfigSerializer + + ChannelFeaturesSerializer + + ChannelOpenResponseSerializer + + CommandResponseSerializer + + InputInfoSerializer + + ColorSerializer + + RouteResponseSerializer + + ThrowableSerializer + + FailureMessageSerializer + + FailureTypeSerializer + + NodeAddressSerializer + + DirectedHtlcSerializer + + PaymentRequestSerializer + + JavaUUIDSerializer + + OriginSerializer + + GlobalBalanceSerializer } 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..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 @@ -16,9 +16,8 @@ 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.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,47 @@ 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))(JsonSerializers.formats) shouldBe expectedIn + JsonSerializers.serialization.write(OutgoingHtlc(add))(JsonSerializers.formats) 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("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("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") { 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) =>