From 413ee29dab7ae36d48ae5915536ad4073998519b Mon Sep 17 00:00:00 2001 From: Bastien Teinturier <31281497+t-bast@users.noreply.github.com> Date: Wed, 16 Dec 2020 10:55:30 +0100 Subject: [PATCH] Fix htlc origin JSON serialization (#1641) A recent refactoring to include a `replyTo` parameter made the JSON serialization ugly and hard to work with. Fixes #1611 --- .../fr/acinq/eclair/api/JsonSerializers.scala | 23 ++++++++++++++++--- .../eclair/api/JsonSerializersSpec.scala | 21 ++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala index af8a767481..265ac56b02 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala @@ -16,9 +16,6 @@ package fr.acinq.eclair.api -import java.net.InetSocketAddress -import java.util.UUID - import com.google.common.net.HostAndPort import de.heikoseeberger.akkahttpjson4s.Json4sSupport import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} @@ -39,6 +36,9 @@ import org.json4s.JsonAST._ import org.json4s.{CustomKeySerializer, CustomSerializer, DefaultFormats, Extraction, TypeHints, jackson} import scodec.bits.ByteVector +import java.net.InetSocketAddress +import java.util.UUID + /** * JSON Serializers. * Note: in general, deserialization does not need to be implemented. @@ -311,6 +311,22 @@ class ChannelEventSerializer extends CustomSerializer[ChannelEvent](_ => ( { ) })) +class OriginSerializer extends CustomSerializer[Origin](_ => ( { + null +}, { + case o: Origin.Local => JObject(JField("paymentId", JString(o.id.toString))) + case o: Origin.ChannelRelayed => JObject( + JField("channelId", JString(o.originChannelId.toHex)), + JField("htlcId", JLong(o.originHtlcId)), + ) + case o: Origin.TrampolineRelayed => JArray(o.htlcs.map { + case (channelId, htlcId) => JObject( + JField("channelId", JString(channelId.toHex)), + JField("htlcId", JLong(htlcId)), + ) + }) +})) + case class CustomTypeHints(custom: Map[Class[_], String]) extends TypeHints { val reverse: Map[String, Class[_]] = custom.map(_.swap) @@ -386,6 +402,7 @@ object JsonSupport extends Json4sSupport { new PaymentRequestSerializer + new JavaUUIDSerializer + new FeaturesSerializer + + new OriginSerializer + CustomTypeHints.incomingPaymentStatus + CustomTypeHints.outgoingPaymentStatus + CustomTypeHints.paymentEvent).withTypeHintFieldName("type") diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala index 971314c062..bfeedf9448 100644 --- a/eclair-node/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala +++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala @@ -16,11 +16,9 @@ package fr.acinq.eclair.api -import java.net.InetAddress -import java.util.UUID - import fr.acinq.bitcoin.{ByteVector32, OutPoint, Transaction} import fr.acinq.eclair._ +import fr.acinq.eclair.channel.Origin import fr.acinq.eclair.payment.{PaymentRequest, PaymentSettlingOnChain} import fr.acinq.eclair.transactions.{IncomingHtlc, OutgoingHtlc} import fr.acinq.eclair.wire._ @@ -28,6 +26,9 @@ import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers import scodec.bits._ +import java.net.InetAddress +import java.util.UUID + class JsonSerializersSpec extends AnyFunSuite with Matchers { test("deserialize Map[OutPoint, ByteVector]") { @@ -82,6 +83,20 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { JsonSupport.serialization.write(OutgoingHtlc(add))(org.json4s.DefaultFormats + new DirectedHtlcSerializer) shouldBe expectedIn.replace("IN", "OUT") } + test("HTLC origin serialization") { + val localOrigin = Origin.LocalCold(UUID.fromString("11111111-1111-1111-1111-111111111111")) + val expectedLocalOrigin = """{"paymentId":"11111111-1111-1111-1111-111111111111"}""" + JsonSupport.serialization.write(localOrigin)(org.json4s.DefaultFormats + new OriginSerializer) shouldBe expectedLocalOrigin + + val channelOrigin = Origin.ChannelRelayedCold(ByteVector32(hex"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f"), 7, 500 msat, 400 msat) + val expectedChannelOrigin = """{"channelId":"345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f","htlcId":7}""" + JsonSupport.serialization.write(channelOrigin)(org.json4s.DefaultFormats + new 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}]""" + JsonSupport.serialization.write(trampolineOrigin)(org.json4s.DefaultFormats + new OriginSerializer) shouldBe expectedTrampolineOrigin + } + test("Payment Request") { val ref = "lnbcrt50n1p0fm9cdpp5al3wvsfkc6p7fxy89eu8gm4aww9mseu9syrcqtpa4mvx42qelkwqdq9v9ekgxqrrss9qypqsqsp5wl2t45v0hj4lgud0zjxcnjccd29ts0p2kh4vpw75vnhyyzyjtjtqarpvqg33asgh3z5ghfuvhvtf39xtnu9e7aqczpgxa9quwsxkd9rnwmx06pve9awgeewxqh90dqgrhzgsqc09ek6uejr93z8puafm6gsqgrk0hy" val pr = PaymentRequest.read(ref)