From 8dddd2139bac38f49627a3787f168374f8233745 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 20 Aug 2019 17:00:01 +0200 Subject: [PATCH 1/6] Move Service and FormParamExtractor to eclair-node, move akka-http dependencies to eclair-node --- eclair-core/pom.xml | 17 ------- .../main/scala/fr/acinq/eclair/Setup.scala | 30 ----------- .../fr/acinq/eclair/api/JsonSerializers.scala | 6 +-- .../acinq/eclair/channel/ChannelEvents.scala | 1 - .../acinq/eclair/channel/ChannelTypes.scala | 1 - .../eclair/api/JsonSerializersSpec.scala | 8 +-- .../channel/states/h/ClosingStateSpec.scala | 1 - eclair-node/pom.xml | 24 +++++++++ .../src/main/scala/fr/acinq/eclair/Boot.scala | 30 ++++++++++- .../fr/acinq/eclair/api/ExtraDirectives.scala | 1 - .../eclair/api/FormParamExtractors.scala | 7 ++- .../scala/fr/acinq/eclair/api/Service.scala | 14 +++-- .../src/test/resources/api/close | 0 .../src/test/resources/api/getinfo | 0 .../src/test/resources/api/help | 0 .../src/test/resources/api/peers | 0 .../src/test/resources/api/usablebalances | 0 .../fr/acinq/eclair/api/ApiServiceSpec.scala | 51 +++++++++---------- 18 files changed, 89 insertions(+), 102 deletions(-) rename {eclair-core => eclair-node}/src/main/scala/fr/acinq/eclair/api/ExtraDirectives.scala (99%) rename {eclair-core => eclair-node}/src/main/scala/fr/acinq/eclair/api/FormParamExtractors.scala (98%) rename {eclair-core => eclair-node}/src/main/scala/fr/acinq/eclair/api/Service.scala (97%) rename {eclair-core => eclair-node}/src/test/resources/api/close (100%) rename {eclair-core => eclair-node}/src/test/resources/api/getinfo (100%) rename {eclair-core => eclair-node}/src/test/resources/api/help (100%) rename {eclair-core => eclair-node}/src/test/resources/api/peers (100%) rename {eclair-core => eclair-node}/src/test/resources/api/usablebalances (100%) rename {eclair-core => eclair-node}/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala (91%) diff --git a/eclair-core/pom.xml b/eclair-core/pom.xml index 9df8a928e3..037c642ff2 100644 --- a/eclair-core/pom.xml +++ b/eclair-core/pom.xml @@ -126,12 +126,6 @@ akka-slf4j_${scala.version.short} ${akka.version} - - - com.typesafe.akka - akka-http-core_${scala.version.short} - ${akka.http.version} - com.softwaremill.sttp @@ -144,11 +138,6 @@ json4s-jackson_${scala.version.short} 3.6.0 - - de.heikoseeberger - akka-http-json4s_${scala.version.short} - 1.19.0 - com.softwaremill.sttp json4s_${scala.version.short} @@ -254,12 +243,6 @@ 1.2.3 test - - com.typesafe.akka - akka-http-testkit_${scala.version.short} - ${akka.http.version} - test - org.mockito mockito-scala-scalatest_2.11 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala index bc8ee08f41..4c770f4faf 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -23,15 +23,12 @@ import java.util.concurrent.TimeUnit import akka.Done import akka.actor.{ActorRef, ActorSystem, Props, SupervisorStrategy} -import akka.http.scaladsl.Http import akka.pattern.after -import akka.stream.{ActorMaterializer, BindFailedException} import akka.util.Timeout import com.softwaremill.sttp.okhttp.OkHttpFutureBackend import com.typesafe.config.{Config, ConfigFactory} import fr.acinq.bitcoin.{Block, ByteVector32} import fr.acinq.eclair.NodeParams.{BITCOIND, ELECTRUM} -import fr.acinq.eclair.api._ import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, BatchingBitcoinJsonRPCClient, ExtendedBitcoinClient} import fr.acinq.eclair.blockchain.bitcoind.zmq.ZMQActor import fr.acinq.eclair.blockchain.bitcoind.{BitcoinCoreWallet, ZmqWatcher} @@ -71,7 +68,6 @@ class Setup(datadir: File, seed_opt: Option[ByteVector] = None, db: Option[Databases] = None)(implicit system: ActorSystem) extends Logging { - implicit val materializer = ActorMaterializer() implicit val timeout = Timeout(30 seconds) implicit val formats = org.json4s.DefaultFormats implicit val ec = ExecutionContext.Implicits.global @@ -287,32 +283,6 @@ class Setup(datadir: File, _ <- Future.firstCompletedOf(zmqBlockConnected.future :: zmqBlockTimeout :: Nil) _ <- Future.firstCompletedOf(zmqTxConnected.future :: zmqTxTimeout :: Nil) _ <- Future.firstCompletedOf(tcpBound.future :: tcpTimeout :: Nil) - _ <- if (config.getBoolean("api.enabled")) { - logger.info(s"json-rpc api enabled on port=${config.getInt("api.port")}") - implicit val materializer = ActorMaterializer() - val getInfo = GetInfoResponse(nodeId = nodeParams.nodeId, - alias = nodeParams.alias, - chainHash = nodeParams.chainHash, - blockHeight = Globals.blockCount.intValue(), - publicAddresses = nodeParams.publicAddresses) - val apiPassword = config.getString("api.password") match { - case "" => throw EmptyAPIPasswordException - case valid => valid - } - val apiRoute = new Service { - override val actorSystem = kit.system - override val mat = materializer - override val password = apiPassword - override val eclairApi: Eclair = new EclairImpl(kit) - }.route - val httpBound = Http().bindAndHandle(apiRoute, config.getString("api.binding-ip"), config.getInt("api.port")).recover { - case _: BindFailedException => throw TCPBindException(config.getInt("api.port")) - } - val httpTimeout = after(5 seconds, using = system.scheduler)(Future.failed(TCPBindException(config.getInt("api.port")))) - Future.firstCompletedOf(httpBound :: httpTimeout :: Nil) - } else { - Future.successful(logger.info("json-rpc api is disabled")) - } } yield kit } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala index 203c6c7f35..f1f92dfa65 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala @@ -20,8 +20,6 @@ import java.net.InetSocketAddress import java.util.UUID import com.google.common.net.HostAndPort -import de.heikoseeberger.akkahttpjson4s.Json4sSupport -import de.heikoseeberger.akkahttpjson4s.Json4sSupport.ShouldWritePretty import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, OutPoint, Satoshi, Transaction} import fr.acinq.eclair.channel.{ChannelVersion, State} @@ -182,7 +180,7 @@ class OutgoingPaymentStatusSerializer extends CustomSerializer[OutgoingPaymentSt case el: OutgoingPaymentStatus.Value => JString(el.toString) })) -object JsonSupport extends Json4sSupport { +object JsonSupport { implicit val serialization = jackson.Serialization @@ -215,8 +213,6 @@ object JsonSupport extends Json4sSupport { new JavaUUIDSerializer + new OutgoingPaymentStatusSerializer - implicit val shouldWritePretty: ShouldWritePretty = ShouldWritePretty.True - case class CustomTypeHints(custom: Map[Class[_], String]) extends TypeHints { val reverse: Map[String, Class[_]] = custom.map(_.swap) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala index e80ff51eca..c3b3d79979 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala @@ -20,7 +20,6 @@ import akka.actor.ActorRef import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{ByteVector32, Satoshi, Transaction} import fr.acinq.eclair.{MilliSatoshi, ShortChannelId} -import fr.acinq.eclair.api.MilliSatoshiSerializer import fr.acinq.eclair.channel.Channel.ChannelError import fr.acinq.eclair.channel.Helpers.Closing.ClosingType import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate} diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index 151cf582d5..05ee8556b8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -21,7 +21,6 @@ import java.util.UUID import akka.actor.ActorRef import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, Transaction} -import fr.acinq.eclair.api.MilliSatoshiSerializer import fr.acinq.eclair.transactions.CommitmentSpec import fr.acinq.eclair.transactions.Transactions.CommitTx import fr.acinq.eclair.wire.{AcceptChannel, ChannelAnnouncement, ChannelReestablish, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingLocked, FundingSigned, Init, OnionRoutingPacket, OpenChannel, Shutdown, UpdateAddHtlc} diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala index 38a6b6d7cc..73c96ae993 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala @@ -21,9 +21,8 @@ import java.util.UUID import fr.acinq.bitcoin.{ByteVector32, OutPoint, Transaction} import fr.acinq.eclair._ -import fr.acinq.eclair.payment.{PaymentRequest, PaymentSettlingOnChain} import fr.acinq.eclair.api.JsonSupport.CustomTypeHints -import fr.acinq.eclair.payment.PaymentRequest +import fr.acinq.eclair.payment.{PaymentRequest, PaymentSettlingOnChain} import fr.acinq.eclair.transactions.{IN, OUT} import fr.acinq.eclair.wire.{NodeAddress, Tor2, Tor3} import org.json4s.jackson.Serialization @@ -84,10 +83,7 @@ class JsonSerializersSpec extends FunSuite with Matchers { test("transaction serializer") { implicit val formats = JsonSupport.formats - val tx = Transaction.read("0200000001c8a8934fb38a44b969528252bc37be66ee166c7897c57384d1e561449e110c93010000006b483045022100dc6c50f445ed53d2fb41067fdcb25686fe79492d90e6e5db43235726ace247210220773d35228af0800c257970bee9cf75175d75217de09a8ecd83521befd040c4ca012102082b751372fe7e3b012534afe0bb8d1f2f09c724b1a10a813ce704e5b9c217ccfdffffff0247ba2300000000001976a914f97a7641228e6b17d4b0b08252ae75bd62a95fe788ace3de24000000000017a914a9fefd4b9a9282a1d7a17d2f14ac7d1eb88141d287f7d50800") - assert(JsonSupport.serialization.write(tx) == "{\"txid\":\"3ef63b5d297c9dcf93f33b45b9f102733c36e8ef61da1ccf2bc132a10584be18\",\"tx\":\"0200000001c8a8934fb38a44b969528252bc37be66ee166c7897c57384d1e561449e110c93010000006b483045022100dc6c50f445ed53d2fb41067fdcb25686fe79492d90e6e5db43235726ace247210220773d35228af0800c257970bee9cf75175d75217de09a8ecd83521befd040c4ca012102082b751372fe7e3b012534afe0bb8d1f2f09c724b1a10a813ce704e5b9c217ccfdffffff0247ba2300000000001976a914f97a7641228e6b17d4b0b08252ae75bd62a95fe788ace3de24000000000017a914a9fefd4b9a9282a1d7a17d2f14ac7d1eb88141d287f7d50800\"}") - } -} +} \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 8cff5d868f..e6bbc4c06b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -21,7 +21,6 @@ import java.util.UUID import akka.actor.Status import akka.actor.Status.Failure import akka.testkit.{TestFSMRef, TestProbe} -import com.typesafe.sslconfig.util.NoopLogger import fr.acinq.bitcoin.{ByteVector32, OutPoint, Satoshi, ScriptFlags, Transaction, TxIn} import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} import fr.acinq.eclair.blockchain._ diff --git a/eclair-node/pom.xml b/eclair-node/pom.xml index de06319706..e2f893519a 100644 --- a/eclair-node/pom.xml +++ b/eclair-node/pom.xml @@ -91,5 +91,29 @@ janino 3.0.7 + + + com.typesafe.akka + akka-http-core_${scala.version.short} + ${akka.http.version} + + + de.heikoseeberger + akka-http-json4s_${scala.version.short} + 1.19.0 + + + + com.typesafe.akka + akka-http-testkit_${scala.version.short} + ${akka.http.version} + test + + + org.mockito + mockito-scala-scalatest_2.11 + 1.4.1 + test + diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala b/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala index 3241994b3b..90d9ab6a24 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala @@ -19,9 +19,13 @@ package fr.acinq.eclair import java.io.File import akka.actor.ActorSystem +import akka.http.scaladsl.Http +import akka.stream.{ActorMaterializer, BindFailedException} +import com.typesafe.config.ConfigFactory +import fr.acinq.eclair.api.Service import grizzled.slf4j.Logging -import scala.concurrent.ExecutionContext +import scala.concurrent.{Await, ExecutionContext} import scala.util.{Failure, Success} /** @@ -39,7 +43,29 @@ object Boot extends App with Logging { val setup = new Setup(datadir) plugins.foreach(_.onSetup(setup)) setup.bootstrap onComplete { - case Success(kit) => plugins.foreach(_.onKit(kit)) + case Success(kit) => + plugins.foreach(_.onKit(kit)) + val config = NodeParams.loadConfiguration(datadir, ConfigFactory.empty()) + if(config.getBoolean("api.enabled")){ + logger.info(s"json API enabled on port=${config.getInt("api.port")}") + implicit val materializer = ActorMaterializer() + val apiPassword = config.getString("api.password") match { + case "" => throw EmptyAPIPasswordException + case valid => valid + } + val apiRoute = new Service { + override val actorSystem = system + override val mat = materializer + override val password = apiPassword + override val eclairApi: Eclair = new EclairImpl(kit) + }.route + Http().bindAndHandle(apiRoute, config.getString("api.binding-ip"), config.getInt("api.port")).onFailure { + case _: BindFailedException => onError(TCPBindException(config.getInt("api.port"))) + } + } else { + logger.info("json API disabled") + } + case Failure(t) => onError(t) } } catch { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/ExtraDirectives.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/ExtraDirectives.scala similarity index 99% rename from eclair-core/src/main/scala/fr/acinq/eclair/api/ExtraDirectives.scala rename to eclair-node/src/main/scala/fr/acinq/eclair/api/ExtraDirectives.scala index 2973b53ec6..6d308f44a8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/ExtraDirectives.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/ExtraDirectives.scala @@ -26,7 +26,6 @@ import fr.acinq.eclair.{MilliSatoshi, ShortChannelId} import fr.acinq.eclair.api.FormParamExtractors.{sha256HashUnmarshaller, shortChannelIdUnmarshaller} import fr.acinq.eclair.api.JsonSupport._ import fr.acinq.eclair.payment.PaymentRequest - import scala.concurrent.Future import scala.util.{Failure, Success} diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/FormParamExtractors.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/FormParamExtractors.scala similarity index 98% rename from eclair-core/src/main/scala/fr/acinq/eclair/api/FormParamExtractors.scala rename to eclair-node/src/main/scala/fr/acinq/eclair/api/FormParamExtractors.scala index cbe045faa8..d5d92c8e31 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/FormParamExtractors.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/FormParamExtractors.scala @@ -18,16 +18,15 @@ package fr.acinq.eclair.api import java.util.UUID -import JsonSupport._ import akka.http.scaladsl.unmarshalling.Unmarshaller import akka.util.Timeout -import fr.acinq.bitcoin.{ByteVector32, Satoshi} import fr.acinq.bitcoin.Crypto.PublicKey -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId} +import fr.acinq.bitcoin.{ByteVector32, Satoshi} +import fr.acinq.eclair.api.JsonSupport._ import fr.acinq.eclair.io.NodeURI import fr.acinq.eclair.payment.PaymentRequest +import fr.acinq.eclair.{MilliSatoshi, ShortChannelId} import scodec.bits.ByteVector - import scala.concurrent.duration._ import scala.util.{Failure, Success, Try} diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala similarity index 97% rename from eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala rename to eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala index 7d50d75217..fb211b5e44 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala @@ -31,6 +31,7 @@ import akka.stream.scaladsl.{BroadcastHub, Flow, Keep, Source} import akka.stream.{ActorMaterializer, OverflowStrategy} import akka.util.Timeout import com.google.common.net.HostAndPort +import de.heikoseeberger.akkahttpjson4s.Json4sSupport import fr.acinq.bitcoin.{ByteVector32, Satoshi} import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.eclair.api.FormParamExtractors._ @@ -40,18 +41,14 @@ import fr.acinq.eclair.payment.PaymentLifecycle.PaymentFailed import fr.acinq.eclair.payment.{PaymentReceived, PaymentRequest, _} import fr.acinq.eclair.{Eclair, MilliSatoshi, ShortChannelId} import grizzled.slf4j.Logging -import org.json4s.jackson.Serialization import scodec.bits.ByteVector - +import JsonSupport.{formats, serialization} import scala.concurrent.Future import scala.concurrent.duration._ case class ErrorResponse(error: String) -trait Service extends ExtraDirectives with Logging { - - // important! Must NOT import the unmarshaller as it is too generic...see https://github.com/akka/akka-http/issues/541 - import JsonSupport.{formats, marshaller, serialization} +trait Service extends ExtraDirectives with Logging with Json4sSupport { // used to send typed messages over the websocket val formatsWithTypeHint = formats.withTypeHintFieldName("type") + @@ -103,8 +100,8 @@ trait Service extends ExtraDirectives with Logging { } def receive: Receive = { - case message: PaymentFailed => flowInput.offer(Serialization.write(message)(formatsWithTypeHint)) - case message: PaymentEvent => flowInput.offer(Serialization.write(message)(formatsWithTypeHint)) + case message: PaymentFailed => flowInput.offer(serialization.write(message)(formatsWithTypeHint)) + case message: PaymentEvent => flowInput.offer(serialization.write(message)(formatsWithTypeHint)) } })) @@ -305,3 +302,4 @@ trait Service extends ExtraDirectives with Logging { } } } + diff --git a/eclair-core/src/test/resources/api/close b/eclair-node/src/test/resources/api/close similarity index 100% rename from eclair-core/src/test/resources/api/close rename to eclair-node/src/test/resources/api/close diff --git a/eclair-core/src/test/resources/api/getinfo b/eclair-node/src/test/resources/api/getinfo similarity index 100% rename from eclair-core/src/test/resources/api/getinfo rename to eclair-node/src/test/resources/api/getinfo diff --git a/eclair-core/src/test/resources/api/help b/eclair-node/src/test/resources/api/help similarity index 100% rename from eclair-core/src/test/resources/api/help rename to eclair-node/src/test/resources/api/help diff --git a/eclair-core/src/test/resources/api/peers b/eclair-node/src/test/resources/api/peers similarity index 100% rename from eclair-core/src/test/resources/api/peers rename to eclair-node/src/test/resources/api/peers diff --git a/eclair-core/src/test/resources/api/usablebalances b/eclair-node/src/test/resources/api/usablebalances similarity index 100% rename from eclair-core/src/test/resources/api/usablebalances rename to eclair-node/src/test/resources/api/usablebalances diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala similarity index 91% rename from eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala rename to eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index 87fbffeefe..2c6151ccdc 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -26,35 +26,33 @@ import akka.http.scaladsl.server.Route import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest, WSProbe} import akka.stream.ActorMaterializer import akka.util.Timeout +import de.heikoseeberger.akkahttpjson4s.Json4sSupport import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{ByteVector32, Satoshi} -import fr.acinq.eclair.TestConstants._ import fr.acinq.eclair._ import fr.acinq.eclair.io.NodeURI import fr.acinq.eclair.io.Peer.PeerInfo import fr.acinq.eclair.payment.PaymentLifecycle.PaymentFailed import fr.acinq.eclair.payment._ import fr.acinq.eclair.wire.NodeAddress -import org.json4s.jackson.Serialization import org.mockito.scalatest.IdiomaticMockito import org.scalatest.{FunSuite, Matchers} import scodec.bits._ - import scala.concurrent.Future import scala.concurrent.duration._ import scala.io.Source import scala.reflect.ClassTag import scala.util.Try -class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMockito with Matchers { +class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMockito with Matchers with Json4sSupport { implicit val formats = JsonSupport.formats implicit val serialization = JsonSupport.serialization - implicit val marshaller = JsonSupport.marshaller - implicit val unmarshaller = JsonSupport.unmarshaller - implicit val routeTestTimeout = RouteTestTimeout(3 seconds) + val aliceNodeId = PublicKey(hex"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0") + val bobNodeId = PublicKey(hex"039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a3585") + class MockService(eclair: Eclair) extends Service { override val eclairApi: Eclair = eclair @@ -100,7 +98,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock check { assert(handled) assert(status == BadRequest) - val resp = entityAs[ErrorResponse](JsonSupport.unmarshaller, ClassTag(classOf[ErrorResponse])) + val resp = entityAs[ErrorResponse](unmarshaller, ClassTag(classOf[ErrorResponse])) assert(resp.error == "The form field 'channelId' was malformed:\nInvalid hexadecimal character 'h' at index 0") } @@ -121,12 +119,12 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock val mockService = new MockService(eclair) eclair.peersInfo()(any[Timeout]) returns Future.successful(List( PeerInfo( - nodeId = Alice.nodeParams.nodeId, + nodeId = aliceNodeId, state = "CONNECTED", - address = Some(Alice.nodeParams.publicAddresses.head.socketAddress), + address = Some(NodeAddress.fromParts("localhost", 9731).get.socketAddress), channels = 1), PeerInfo( - nodeId = Bob.nodeParams.nodeId, + nodeId = bobNodeId, state = "DISCONNECTED", address = None, channels = 1))) @@ -145,11 +143,12 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock test("'usablebalances' asks router for current usable balances") { + val remoteNodeId = PublicKey(hex"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0") val eclair = mock[Eclair] val mockService = new MockService(eclair) eclair.usableBalances()(any[Timeout]) returns Future.successful(List( - UsableBalances(canSend = MilliSatoshi(100000000), canReceive = MilliSatoshi(20000000), shortChannelId = ShortChannelId(1), remoteNodeId = TestConstants.Alice.keyManager.nodeKey.publicKey, isPublic = true), - UsableBalances(canSend = MilliSatoshi(400000000), canReceive = MilliSatoshi(30000000), shortChannelId = ShortChannelId(2), remoteNodeId = TestConstants.Alice.keyManager.nodeKey.publicKey, isPublic = false) + UsableBalances(canSend = MilliSatoshi(100000000), canReceive = MilliSatoshi(20000000), shortChannelId = ShortChannelId(1), remoteNodeId = remoteNodeId, isPublic = true), + UsableBalances(canSend = MilliSatoshi(400000000), canReceive = MilliSatoshi(30000000), shortChannelId = ShortChannelId(2), remoteNodeId = remoteNodeId, isPublic = false) )) Post("/usablebalances") ~> @@ -169,9 +168,9 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock val eclair = mock[Eclair] val mockService = new MockService(eclair) eclair.getInfoResponse()(any[Timeout]) returns Future.successful(GetInfoResponse( - nodeId = Alice.nodeParams.nodeId, - alias = Alice.nodeParams.alias, - chainHash = Alice.nodeParams.chainHash, + nodeId = aliceNodeId, + alias = "alice", + chainHash = ByteVector32(hex"06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f"), blockHeight = 9999, publicAddresses = NodeAddress.fromParts("localhost", 9731).get :: Nil )) @@ -183,7 +182,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock assert(handled) assert(status == OK) val resp = entityAs[String] - assert(resp.toString.contains(Alice.nodeParams.nodeId.toString)) + assert(resp.toString.contains(aliceNodeId.toString)) eclair.getInfoResponse()(any[Timeout]).wasCalled(once) matchTestJson("getinfo", resp) } @@ -195,7 +194,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock val channelId = "56d7d6eda04d80138270c49709f1eadb5ab4939e5061309ccdacdb98ce637d0e" val eclair = mock[Eclair] - eclair.close(any, any)(any[Timeout]) returns Future.successful(Alice.nodeParams.nodeId.toString()) + eclair.close(any, any)(any[Timeout]) returns Future.successful(aliceNodeId.toString()) val mockService = new MockService(eclair) Post("/close", FormData("shortChannelId" -> shortChannelIdSerialized).toEntity) ~> @@ -206,7 +205,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock assert(handled) assert(status == OK) val resp = entityAs[String] - assert(resp.contains(Alice.nodeParams.nodeId.toString)) + assert(resp.contains(aliceNodeId.toString)) eclair.close(Right(ShortChannelId(shortChannelIdSerialized)), None)(any[Timeout]).wasCalled(once) matchTestJson("close", resp) } @@ -219,7 +218,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock assert(handled) assert(status == OK) val resp = entityAs[String] - assert(resp.contains(Alice.nodeParams.nodeId.toString)) + assert(resp.contains(aliceNodeId.toString)) eclair.close(Left(ByteVector32.fromValidHex(channelId)), None)(any[Timeout]).wasCalled(once) matchTestJson("close", resp) } @@ -297,7 +296,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock check { assert(handled) assert(status == NotFound) - val resp = entityAs[ErrorResponse](JsonSupport.unmarshaller, ClassTag(classOf[ErrorResponse])) + val resp = entityAs[ErrorResponse](unmarshaller, ClassTag(classOf[ErrorResponse])) assert(resp == ErrorResponse("Not found")) eclair.receivedInfo(ByteVector32.Zeroes)(any[Timeout]).wasCalled(once) } @@ -353,31 +352,31 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock val pf = PaymentFailed(fixedUUID, ByteVector32.Zeroes, failures = Seq.empty) val expectedSerializedPf = """{"type":"payment-failed","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","failures":[]}""" - Serialization.write(pf)(mockService.formatsWithTypeHint) === expectedSerializedPf + serialization.write(pf)(mockService.formatsWithTypeHint) === expectedSerializedPf system.eventStream.publish(pf) wsClient.expectMessage(expectedSerializedPf) val ps = PaymentSent(fixedUUID, amount = MilliSatoshi(21), feesPaid = MilliSatoshi(1), paymentHash = ByteVector32.Zeroes, paymentPreimage = ByteVector32.One, toChannelId = ByteVector32.Zeroes, timestamp = 1553784337711L) val expectedSerializedPs = """{"type":"payment-sent","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","amount":21,"feesPaid":1,"paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","toChannelId":"0000000000000000000000000000000000000000000000000000000000000000","timestamp":1553784337711}""" - Serialization.write(ps)(mockService.formatsWithTypeHint) === expectedSerializedPs + serialization.write(ps)(mockService.formatsWithTypeHint) === expectedSerializedPs system.eventStream.publish(ps) wsClient.expectMessage(expectedSerializedPs) val prel = PaymentRelayed(amountIn = MilliSatoshi(21), amountOut = MilliSatoshi(20), paymentHash = ByteVector32.Zeroes, fromChannelId = ByteVector32.Zeroes, ByteVector32.One, timestamp = 1553784963659L) val expectedSerializedPrel = """{"type":"payment-relayed","amountIn":21,"amountOut":20,"paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","fromChannelId":"0000000000000000000000000000000000000000000000000000000000000000","toChannelId":"0100000000000000000000000000000000000000000000000000000000000000","timestamp":1553784963659}""" - Serialization.write(prel)(mockService.formatsWithTypeHint) === expectedSerializedPrel + serialization.write(prel)(mockService.formatsWithTypeHint) === expectedSerializedPrel system.eventStream.publish(prel) wsClient.expectMessage(expectedSerializedPrel) val precv = PaymentReceived(amount = MilliSatoshi(21), paymentHash = ByteVector32.Zeroes, fromChannelId = ByteVector32.One, timestamp = 1553784963659L) val expectedSerializedPrecv = """{"type":"payment-received","amount":21,"paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","fromChannelId":"0100000000000000000000000000000000000000000000000000000000000000","timestamp":1553784963659}""" - Serialization.write(precv)(mockService.formatsWithTypeHint) === expectedSerializedPrecv + serialization.write(precv)(mockService.formatsWithTypeHint) === expectedSerializedPrecv system.eventStream.publish(precv) wsClient.expectMessage(expectedSerializedPrecv) val pset = PaymentSettlingOnChain(fixedUUID, amount = MilliSatoshi(21), paymentHash = ByteVector32.One, timestamp = 1553785442676L) val expectedSerializedPset = """{"type":"payment-settling-onchain","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","amount":21,"paymentHash":"0100000000000000000000000000000000000000000000000000000000000000","timestamp":1553785442676}""" - Serialization.write(pset)(mockService.formatsWithTypeHint) === expectedSerializedPset + serialization.write(pset)(mockService.formatsWithTypeHint) === expectedSerializedPset system.eventStream.publish(pset) wsClient.expectMessage(expectedSerializedPset) } From 07bd79c45921ffe47312c238acd16e2b62765d88 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 21 Aug 2019 13:39:37 +0200 Subject: [PATCH 2/6] Import akka-http-json4s in eclair-core --- eclair-core/pom.xml | 5 +++++ .../scala/fr/acinq/eclair/api/JsonSerializers.scala | 6 +++++- eclair-node/pom.xml | 5 ----- .../src/main/scala/fr/acinq/eclair/api/Service.scala | 9 +++++---- .../scala/fr/acinq/eclair/api/ApiServiceSpec.scala | 12 +++++------- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/eclair-core/pom.xml b/eclair-core/pom.xml index 037c642ff2..f5c41c2355 100644 --- a/eclair-core/pom.xml +++ b/eclair-core/pom.xml @@ -138,6 +138,11 @@ json4s-jackson_${scala.version.short} 3.6.0 + + de.heikoseeberger + akka-http-json4s_${scala.version.short} + 1.19.0 + com.softwaremill.sttp json4s_${scala.version.short} diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala index f1f92dfa65..203c6c7f35 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala @@ -20,6 +20,8 @@ import java.net.InetSocketAddress import java.util.UUID import com.google.common.net.HostAndPort +import de.heikoseeberger.akkahttpjson4s.Json4sSupport +import de.heikoseeberger.akkahttpjson4s.Json4sSupport.ShouldWritePretty import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, OutPoint, Satoshi, Transaction} import fr.acinq.eclair.channel.{ChannelVersion, State} @@ -180,7 +182,7 @@ class OutgoingPaymentStatusSerializer extends CustomSerializer[OutgoingPaymentSt case el: OutgoingPaymentStatus.Value => JString(el.toString) })) -object JsonSupport { +object JsonSupport extends Json4sSupport { implicit val serialization = jackson.Serialization @@ -213,6 +215,8 @@ object JsonSupport { new JavaUUIDSerializer + new OutgoingPaymentStatusSerializer + implicit val shouldWritePretty: ShouldWritePretty = ShouldWritePretty.True + case class CustomTypeHints(custom: Map[Class[_], String]) extends TypeHints { val reverse: Map[String, Class[_]] = custom.map(_.swap) diff --git a/eclair-node/pom.xml b/eclair-node/pom.xml index e2f893519a..803aa7bc38 100644 --- a/eclair-node/pom.xml +++ b/eclair-node/pom.xml @@ -97,11 +97,6 @@ akka-http-core_${scala.version.short} ${akka.http.version} - - de.heikoseeberger - akka-http-json4s_${scala.version.short} - 1.19.0 - com.typesafe.akka diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala index fb211b5e44..b27329acfa 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala @@ -31,7 +31,6 @@ import akka.stream.scaladsl.{BroadcastHub, Flow, Keep, Source} import akka.stream.{ActorMaterializer, OverflowStrategy} import akka.util.Timeout import com.google.common.net.HostAndPort -import de.heikoseeberger.akkahttpjson4s.Json4sSupport import fr.acinq.bitcoin.{ByteVector32, Satoshi} import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.eclair.api.FormParamExtractors._ @@ -39,16 +38,18 @@ import fr.acinq.eclair.api.JsonSupport.CustomTypeHints import fr.acinq.eclair.io.NodeURI import fr.acinq.eclair.payment.PaymentLifecycle.PaymentFailed import fr.acinq.eclair.payment.{PaymentReceived, PaymentRequest, _} -import fr.acinq.eclair.{Eclair, MilliSatoshi, ShortChannelId} +import fr.acinq.eclair.{Eclair, MilliSatoshi} import grizzled.slf4j.Logging import scodec.bits.ByteVector -import JsonSupport.{formats, serialization} import scala.concurrent.Future import scala.concurrent.duration._ case class ErrorResponse(error: String) -trait Service extends ExtraDirectives with Logging with Json4sSupport { +trait Service extends ExtraDirectives with Logging { + + // important! Must NOT import the unmarshaller as it is too generic...see https://github.com/akka/akka-http/issues/541 + import JsonSupport.{formats, marshaller, serialization} // used to send typed messages over the websocket val formatsWithTypeHint = formats.withTypeHintFieldName("type") + diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index 2c6151ccdc..bfd9ce16a7 100644 --- a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -25,8 +25,7 @@ import akka.http.scaladsl.model.headers.BasicHttpCredentials import akka.http.scaladsl.server.Route import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest, WSProbe} import akka.stream.ActorMaterializer -import akka.util.Timeout -import de.heikoseeberger.akkahttpjson4s.Json4sSupport +import akka.util.{ByteString, Timeout} import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{ByteVector32, Satoshi} import fr.acinq.eclair._ @@ -38,13 +37,13 @@ import fr.acinq.eclair.wire.NodeAddress import org.mockito.scalatest.IdiomaticMockito import org.scalatest.{FunSuite, Matchers} import scodec.bits._ -import scala.concurrent.Future +import scala.concurrent.{Await, Future} import scala.concurrent.duration._ import scala.io.Source import scala.reflect.ClassTag import scala.util.Try -class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMockito with Matchers with Json4sSupport { +class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMockito with Matchers { implicit val formats = JsonSupport.formats implicit val serialization = JsonSupport.serialization @@ -98,7 +97,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock check { assert(handled) assert(status == BadRequest) - val resp = entityAs[ErrorResponse](unmarshaller, ClassTag(classOf[ErrorResponse])) + val resp = entityAs[ErrorResponse](JsonSupport.unmarshaller, ClassTag(classOf[ErrorResponse])) assert(resp.error == "The form field 'channelId' was malformed:\nInvalid hexadecimal character 'h' at index 0") } @@ -110,7 +109,6 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock assert(handled) assert(status == BadRequest) } - } test("'peers' should ask the switchboard for current known peers") { @@ -296,7 +294,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock check { assert(handled) assert(status == NotFound) - val resp = entityAs[ErrorResponse](unmarshaller, ClassTag(classOf[ErrorResponse])) + val resp = entityAs[ErrorResponse](JsonSupport.unmarshaller, ClassTag(classOf[ErrorResponse])) assert(resp == ErrorResponse("Not found")) eclair.receivedInfo(ByteVector32.Zeroes)(any[Timeout]).wasCalled(once) } From 3469eb5110be2e12df7b4148ee606a7f47b4f9e8 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 22 Aug 2019 12:18:23 +0200 Subject: [PATCH 3/6] Do not reload the config in eclair-node, remove unnecessary line in api test --- eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala | 2 +- .../src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala b/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala index 90d9ab6a24..543f3af5e4 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala @@ -45,7 +45,7 @@ object Boot extends App with Logging { setup.bootstrap onComplete { case Success(kit) => plugins.foreach(_.onKit(kit)) - val config = NodeParams.loadConfiguration(datadir, ConfigFactory.empty()) + val config = setup.config if(config.getBoolean("api.enabled")){ logger.info(s"json API enabled on port=${config.getInt("api.port")}") implicit val materializer = ActorMaterializer() diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index bfd9ce16a7..e1289f2624 100644 --- a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -141,12 +141,11 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock test("'usablebalances' asks router for current usable balances") { - val remoteNodeId = PublicKey(hex"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0") val eclair = mock[Eclair] val mockService = new MockService(eclair) eclair.usableBalances()(any[Timeout]) returns Future.successful(List( - UsableBalances(canSend = MilliSatoshi(100000000), canReceive = MilliSatoshi(20000000), shortChannelId = ShortChannelId(1), remoteNodeId = remoteNodeId, isPublic = true), - UsableBalances(canSend = MilliSatoshi(400000000), canReceive = MilliSatoshi(30000000), shortChannelId = ShortChannelId(2), remoteNodeId = remoteNodeId, isPublic = false) + UsableBalances(canSend = MilliSatoshi(100000000), canReceive = MilliSatoshi(20000000), shortChannelId = ShortChannelId(1), remoteNodeId = aliceNodeId, isPublic = true), + UsableBalances(canSend = MilliSatoshi(400000000), canReceive = MilliSatoshi(30000000), shortChannelId = ShortChannelId(2), remoteNodeId = aliceNodeId, isPublic = false) )) Post("/usablebalances") ~> From e917f5b8d820c197fe070c5f0f9487eb22d290d0 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 22 Aug 2019 16:39:31 +0200 Subject: [PATCH 4/6] Allow http APIs to be enabled with using the GUI as well --- .../scala/fr/acinq/eclair/JavafxBoot.scala | 7 ++- .../scala/fr/acinq/eclair/gui/FxApp.scala | 3 +- .../src/main/scala/fr/acinq/eclair/Boot.scala | 55 +++++++++++-------- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/eclair-node-gui/src/main/scala/fr/acinq/eclair/JavafxBoot.scala b/eclair-node-gui/src/main/scala/fr/acinq/eclair/JavafxBoot.scala index c38040b47f..9152e82cad 100644 --- a/eclair-node-gui/src/main/scala/fr/acinq/eclair/JavafxBoot.scala +++ b/eclair-node-gui/src/main/scala/fr/acinq/eclair/JavafxBoot.scala @@ -22,7 +22,7 @@ import akka.actor.ActorSystem import fr.acinq.eclair.gui.{FxApp, FxPreloader} import grizzled.slf4j.Logging import javafx.application.Application - +import scala.concurrent.ExecutionContext.Implicits.global /** * Created by PM on 25/01/2016. */ @@ -33,7 +33,10 @@ object JavafxBoot extends App with Logging { if (headless) { implicit val system = ActorSystem("eclair-node-gui") - new Setup(datadir).bootstrap + val setup = new Setup(datadir) + setup.bootstrap.map { kit => + Boot.startApiServiceIfEnabled(setup.config, kit) + } } else { System.setProperty("javafx.preloader", classOf[FxPreloader].getName) Application.launch(classOf[FxApp], datadir.getAbsolutePath) diff --git a/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/FxApp.scala b/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/FxApp.scala index 30f4f1df73..163d8c14a9 100644 --- a/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/FxApp.scala +++ b/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/FxApp.scala @@ -104,7 +104,8 @@ class FxApp extends Application with Logging { system.eventStream.subscribe(guiUpdater, classOf[ElectrumEvent]) pKit.completeWith(setup.bootstrap) pKit.future.onComplete { - case Success(_) => + case Success(kit) => + Boot.startApiServiceIfEnabled(setup.config, kit) Platform.runLater(new Runnable { override def run(): Unit = { val scene = new Scene(mainRoot) diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala b/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala index 543f3af5e4..45ad4bde24 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/Boot.scala @@ -21,10 +21,9 @@ import java.io.File import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.stream.{ActorMaterializer, BindFailedException} -import com.typesafe.config.ConfigFactory +import com.typesafe.config.Config import fr.acinq.eclair.api.Service import grizzled.slf4j.Logging - import scala.concurrent.{Await, ExecutionContext} import scala.util.{Failure, Success} @@ -44,34 +43,44 @@ object Boot extends App with Logging { plugins.foreach(_.onSetup(setup)) setup.bootstrap onComplete { case Success(kit) => + startApiServiceIfEnabled(setup.config, kit) plugins.foreach(_.onKit(kit)) - val config = setup.config - if(config.getBoolean("api.enabled")){ - logger.info(s"json API enabled on port=${config.getInt("api.port")}") - implicit val materializer = ActorMaterializer() - val apiPassword = config.getString("api.password") match { - case "" => throw EmptyAPIPasswordException - case valid => valid - } - val apiRoute = new Service { - override val actorSystem = system - override val mat = materializer - override val password = apiPassword - override val eclairApi: Eclair = new EclairImpl(kit) - }.route - Http().bindAndHandle(apiRoute, config.getString("api.binding-ip"), config.getInt("api.port")).onFailure { - case _: BindFailedException => onError(TCPBindException(config.getInt("api.port"))) - } - } else { - logger.info("json API disabled") - } - case Failure(t) => onError(t) } } catch { case t: Throwable => onError(t) } + /** + * Starts the http APIs service if enabled in the configuration + * + * @param config + * @param kit + * @param system + * @param ec + */ + def startApiServiceIfEnabled(config: Config, kit: Kit)(implicit system: ActorSystem, ec: ExecutionContext) = { + if(config.getBoolean("api.enabled")){ + logger.info(s"json API enabled on port=${config.getInt("api.port")}") + implicit val materializer = ActorMaterializer() + val apiPassword = config.getString("api.password") match { + case "" => throw EmptyAPIPasswordException + case valid => valid + } + val apiRoute = new Service { + override val actorSystem = system + override val mat = materializer + override val password = apiPassword + override val eclairApi: Eclair = new EclairImpl(kit) + }.route + Http().bindAndHandle(apiRoute, config.getString("api.binding-ip"), config.getInt("api.port")).onFailure { + case _: BindFailedException => onError(TCPBindException(config.getInt("api.port"))) + } + } else { + logger.info("json API disabled") + } + } + def onError(t: Throwable): Unit = { val errorMsg = if (t.getMessage != null) t.getMessage else t.getClass.getSimpleName System.err.println(s"fatal error: $errorMsg") From a3fb625267a8ff0ab13aefbc268a93aa9d529177 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 29 Aug 2019 14:01:31 +0200 Subject: [PATCH 5/6] Move dependency akka-http-json4s into eclair-node --- eclair-core/pom.xml | 5 ----- .../main/scala/fr/acinq/eclair/api/JsonSerializers.scala | 6 +----- eclair-node/pom.xml | 5 +++++ .../src/main/scala/fr/acinq/eclair/api/Service.scala | 5 +++-- .../src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala | 5 +++-- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/eclair-core/pom.xml b/eclair-core/pom.xml index f5c41c2355..037c642ff2 100644 --- a/eclair-core/pom.xml +++ b/eclair-core/pom.xml @@ -138,11 +138,6 @@ json4s-jackson_${scala.version.short} 3.6.0 - - de.heikoseeberger - akka-http-json4s_${scala.version.short} - 1.19.0 - com.softwaremill.sttp json4s_${scala.version.short} diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala index 13059d8381..8b21a69d1d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala @@ -20,8 +20,6 @@ import java.net.InetSocketAddress import java.util.UUID import com.google.common.net.HostAndPort -import de.heikoseeberger.akkahttpjson4s.Json4sSupport -import de.heikoseeberger.akkahttpjson4s.Json4sSupport.ShouldWritePretty import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, OutPoint, Satoshi, Transaction} import fr.acinq.eclair.channel.{ChannelVersion, State} @@ -190,7 +188,7 @@ class OutgoingPaymentStatusSerializer extends CustomSerializer[OutgoingPaymentSt case el: OutgoingPaymentStatus.Value => JString(el.toString) })) -object JsonSupport extends Json4sSupport { +object JsonSupport { implicit val serialization = jackson.Serialization @@ -225,8 +223,6 @@ object JsonSupport extends Json4sSupport { new JavaUUIDSerializer + new OutgoingPaymentStatusSerializer - implicit val shouldWritePretty: ShouldWritePretty = ShouldWritePretty.True - case class CustomTypeHints(custom: Map[Class[_], String]) extends TypeHints { val reverse: Map[String, Class[_]] = custom.map(_.swap) diff --git a/eclair-node/pom.xml b/eclair-node/pom.xml index 803aa7bc38..e2f893519a 100644 --- a/eclair-node/pom.xml +++ b/eclair-node/pom.xml @@ -97,6 +97,11 @@ akka-http-core_${scala.version.short} ${akka.http.version} + + de.heikoseeberger + akka-http-json4s_${scala.version.short} + 1.19.0 + com.typesafe.akka diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala index 542723bc3a..3e94eb9307 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala @@ -31,6 +31,7 @@ import akka.stream.scaladsl.{BroadcastHub, Flow, Keep, Source} import akka.stream.{ActorMaterializer, OverflowStrategy} import akka.util.Timeout import com.google.common.net.HostAndPort +import de.heikoseeberger.akkahttpjson4s.Json4sSupport import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{ByteVector32, Satoshi} import fr.acinq.eclair.api.FormParamExtractors._ @@ -47,10 +48,10 @@ import scala.concurrent.duration._ case class ErrorResponse(error: String) -trait Service extends ExtraDirectives with Logging { +trait Service extends ExtraDirectives with Logging with Json4sSupport { // important! Must NOT import the unmarshaller as it is too generic...see https://github.com/akka/akka-http/issues/541 - import JsonSupport.{formats, marshaller, serialization} + import JsonSupport.{formats, serialization} // used to send typed messages over the websocket val formatsWithTypeHint = formats.withTypeHintFieldName("type") + diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index b110c8b14f..5b7d26cc04 100644 --- a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -26,6 +26,7 @@ import akka.http.scaladsl.server.Route import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest, WSProbe} import akka.stream.ActorMaterializer import akka.util.{ByteString, Timeout} +import de.heikoseeberger.akkahttpjson4s.Json4sSupport import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{ByteVector32, Satoshi} import fr.acinq.eclair._ @@ -97,7 +98,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock check { assert(handled) assert(status == BadRequest) - val resp = entityAs[ErrorResponse](JsonSupport.unmarshaller, ClassTag(classOf[ErrorResponse])) + val resp = entityAs[ErrorResponse](Json4sSupport.unmarshaller, ClassTag(classOf[ErrorResponse])) assert(resp.error == "The form field 'channelId' was malformed:\nInvalid hexadecimal character 'h' at index 0") } @@ -293,7 +294,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock check { assert(handled) assert(status == NotFound) - val resp = entityAs[ErrorResponse](JsonSupport.unmarshaller, ClassTag(classOf[ErrorResponse])) + val resp = entityAs[ErrorResponse](Json4sSupport.unmarshaller, ClassTag(classOf[ErrorResponse])) assert(resp == ErrorResponse("Not found")) eclair.receivedInfo(ByteVector32.Zeroes)(any[Timeout]).wasCalled(once) } From 9caac22d6029430c3f0f1f193481ea1572aa6dc3 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 29 Aug 2019 15:53:04 +0200 Subject: [PATCH 6/6] Move json serializers to eclair-node --- .../acinq/eclair/wire/ChannelCodecsSpec.scala | 170 +++++++++++++++++- .../wire/LightningMessageCodecsSpec.scala | 5 - .../fr/acinq/eclair/api/JsonSerializers.scala | 3 +- .../scala/fr/acinq/eclair/api/Service.scala | 4 +- .../eclair/api/JsonSerializersSpec.scala | 0 5 files changed, 165 insertions(+), 17 deletions(-) rename {eclair-core => eclair-node}/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala (98%) rename {eclair-core => eclair-node}/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala (100%) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala index 82e0074997..4fd04a7783 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala @@ -16,22 +16,25 @@ package fr.acinq.eclair.wire +import java.net.InetSocketAddress import java.util.UUID import akka.actor.ActorSystem -import fr.acinq.bitcoin.Crypto.PrivateKey +import com.google.common.net.HostAndPort +import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.DeterministicWallet.KeyPath -import fr.acinq.bitcoin.{Block, ByteVector32, Crypto, DeterministicWallet, OutPoint, Satoshi, Transaction} -import fr.acinq.eclair.api.JsonSupport +import fr.acinq.bitcoin.{Block, ByteVector32, ByteVector64, Crypto, DeterministicWallet, OutPoint, Satoshi, Transaction} import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.{LocalKeyManager, ShaChain} import fr.acinq.eclair.payment.{Local, Relayed} import fr.acinq.eclair.router.Announcements -import fr.acinq.eclair.transactions.Transactions.CommitTx +import fr.acinq.eclair.transactions.Transactions.{CommitTx, InputInfo, TransactionWithInputInfo} import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.ChannelCodecs._ import fr.acinq.eclair.{TestConstants, UInt64, randomBytes, randomBytes32, randomKey, _} +import org.json4s.{CustomKeySerializer, CustomSerializer} +import org.json4s.JsonAST._ import org.json4s.jackson.Serialization import org.scalatest.FunSuite import scodec.bits._ @@ -43,8 +46,8 @@ import scala.io.Source import scala.util.Random /** - * Created by PM on 31/05/2016. - */ + * Created by PM on 31/05/2016. + */ class ChannelCodecsSpec extends FunSuite { @@ -333,9 +336,7 @@ class ChannelCodecsSpec extends FunSuite { assert(oldjson === refjson) assert(newjson === refjson) } - } - } object ChannelCodecsSpec { @@ -402,4 +403,155 @@ object ChannelCodecsSpec { val channelUpdate = Announcements.makeChannelUpdate(ByteVector32(ByteVector.fill(32)(1)), randomKey, randomKey.publicKey, ShortChannelId(142553), CltvExpiryDelta(42), 15 msat, 575 msat, 53, Channel.MAX_FUNDING.toMilliSatoshi) val normal = DATA_NORMAL(commitments, ShortChannelId(42), true, None, channelUpdate, None, None) -} + + object JsonSupport { + + class ByteVectorSerializer extends CustomSerializer[ByteVector](format => ( { + null + }, { + case x: ByteVector => JString(x.toHex) + })) + + class ByteVector32Serializer extends CustomSerializer[ByteVector32](format => ( { + null + }, { + case x: ByteVector32 => JString(x.toHex) + })) + + class ByteVector64Serializer extends CustomSerializer[ByteVector64](format => ( { + null + }, { + case x: ByteVector64 => JString(x.toHex) + })) + + class UInt64Serializer extends CustomSerializer[UInt64](format => ( { + null + }, { + case x: UInt64 => JInt(x.toBigInt) + })) + + class SatoshiSerializer extends CustomSerializer[Satoshi](format => ( { + null + }, { + case x: Satoshi => JInt(x.toLong) + })) + + class MilliSatoshiSerializer extends CustomSerializer[MilliSatoshi](format => ( { + null + }, { + case x: MilliSatoshi => JInt(x.toLong) + })) + + class CltvExpirySerializer extends CustomSerializer[CltvExpiry](format => ( { + null + }, { + case x: CltvExpiry => JLong(x.toLong) + })) + + class CltvExpiryDeltaSerializer extends CustomSerializer[CltvExpiryDelta](format => ( { + null + }, { + case x: CltvExpiryDelta => JInt(x.toInt) + })) + + class ShortChannelIdSerializer extends CustomSerializer[ShortChannelId](format => ( { + null + }, { + case x: ShortChannelId => JString(x.toString()) + })) + + class StateSerializer extends CustomSerializer[State](format => ( { + null + }, { + case x: State => JString(x.toString()) + })) + + class ShaChainSerializer extends CustomSerializer[ShaChain](format => ( { + null + }, { + case x: ShaChain => JNull + })) + + class PublicKeySerializer extends CustomSerializer[PublicKey](format => ( { + null + }, { + case x: PublicKey => JString(x.toString()) + })) + + class PrivateKeySerializer extends CustomSerializer[PrivateKey](format => ( { + null + }, { + case x: PrivateKey => JString("XXX") + })) + + class ChannelVersionSerializer extends CustomSerializer[ChannelVersion](format => ( { + null + }, { + case x: ChannelVersion => JString(x.bits.toBin) + })) + + class TransactionSerializer extends CustomSerializer[TransactionWithInputInfo](ser = format => ( { + null + }, { + case x: Transaction => JObject(List( + JField("txid", JString(x.txid.toHex)), + JField("tx", JString(x.toString())) + )) + })) + + class TransactionWithInputInfoSerializer extends CustomSerializer[TransactionWithInputInfo](ser = format => ( { + null + }, { + case x: TransactionWithInputInfo => JObject(List( + JField("txid", JString(x.tx.txid.toHex)), + JField("tx", JString(x.tx.toString())) + )) + })) + + class InetSocketAddressSerializer extends CustomSerializer[InetSocketAddress](format => ( { + null + }, { + case address: InetSocketAddress => JString(HostAndPort.fromParts(address.getHostString, address.getPort).toString) + })) + + class OutPointSerializer extends CustomSerializer[OutPoint](format => ( { + null + }, { + case x: OutPoint => JString(s"${x.txid}:${x.index}") + })) + + class OutPointKeySerializer extends CustomKeySerializer[OutPoint](format => ( { + null + }, { + case x: OutPoint => s"${x.txid}:${x.index}" + })) + + class InputInfoSerializer extends CustomSerializer[InputInfo](format => ( { + null + }, { + case x: InputInfo => JObject(("outPoint", JString(s"${x.outPoint.txid}:${x.outPoint.index}")), ("amountSatoshis", JInt(x.txOut.amount.toLong))) + })) + + implicit val formats = org.json4s.DefaultFormats + + new ByteVectorSerializer + + new ByteVector32Serializer + + new ByteVector64Serializer + + new UInt64Serializer + + new SatoshiSerializer + + new MilliSatoshiSerializer + + new CltvExpirySerializer + + new CltvExpiryDeltaSerializer + + new ShortChannelIdSerializer + + new StateSerializer + + new ShaChainSerializer + + new PublicKeySerializer + + new PrivateKeySerializer + + new TransactionSerializer + + new TransactionWithInputInfoSerializer + + new InetSocketAddressSerializer + + new OutPointSerializer + + new OutPointKeySerializer + + new ChannelVersionSerializer + + new InputInfoSerializer + } +} \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala index 9bdaa28e3d..03f01c3d59 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala @@ -123,11 +123,6 @@ class LightningMessageCodecsSpec extends FunSuite { case class TestItem(msg: Any, hex: String) test("test vectors for extended channel queries ") { - import org.json4s.{CustomSerializer, ShortTypeHints} - import org.json4s.JsonAST.JString - import org.json4s.jackson.Serialization - import fr.acinq.eclair.api._ - val query_channel_range = QueryChannelRange(Block.RegtestGenesisBlock.blockId, 100000, 1500, TlvStream.empty) val query_channel_range_timestamps_checksums = QueryChannelRange(Block.RegtestGenesisBlock.blockId, 35000, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala similarity index 98% rename from eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala rename to eclair-node/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala index 8b21a69d1d..73c85f0c82 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala @@ -20,6 +20,7 @@ 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} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, OutPoint, Satoshi, Transaction} import fr.acinq.eclair.channel.{ChannelVersion, State} @@ -188,7 +189,7 @@ class OutgoingPaymentStatusSerializer extends CustomSerializer[OutgoingPaymentSt case el: OutgoingPaymentStatus.Value => JString(el.toString) })) -object JsonSupport { +object JsonSupport extends Json4sSupport { implicit val serialization = jackson.Serialization diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala index 3e94eb9307..1e12e7f176 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/Service.scala @@ -48,10 +48,10 @@ import scala.concurrent.duration._ case class ErrorResponse(error: String) -trait Service extends ExtraDirectives with Logging with Json4sSupport { +trait Service extends ExtraDirectives with Logging { // important! Must NOT import the unmarshaller as it is too generic...see https://github.com/akka/akka-http/issues/541 - import JsonSupport.{formats, serialization} + import JsonSupport.{formats, marshaller, serialization} // used to send typed messages over the websocket val formatsWithTypeHint = formats.withTypeHintFieldName("type") + diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala similarity index 100% rename from eclair-core/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala rename to eclair-node/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala