1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-19 09:54:02 +01:00

Json serializers refactoring (#1979)

* make custom serializers objects instead of classes

* reorder json format definition

* 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.

* 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]
```

* 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.
This commit is contained in:
Pierre-Marie Padiou 2021-10-04 10:13:08 +02:00 committed by GitHub
parent fd565040d3
commit 3295881e48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 182 additions and 144 deletions

View File

@ -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(format)
def serialize(implicit format: Formats): PartialFunction[Any, String] = ser
}
class ByteVectorSerializer extends CustomSerializerOnly[ByteVector](_ => {
/**
* 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)
})
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
}

View File

@ -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") {

File diff suppressed because one or more lines are too long