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:
parent
fd565040d3
commit
3295881e48
@ -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
|
||||
|
||||
}
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user