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 071125e68..6238a2edc 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -285,7 +285,7 @@ class Setup(datadir: File, // override val socketHandler = makeSocketHandler(system)(materializer) } - val httpBound = Http().bindAndHandle(api.motherRoute, config.getString("api.binding-ip"), config.getInt("api.port")).recover { + val httpBound = Http().bindAndHandle(api.route, 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")))) 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 ce3c12881..454d13077 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 @@ -30,7 +30,7 @@ import fr.acinq.eclair.transactions.Transactions.{InputInfo, TransactionWithInpu import fr.acinq.eclair.wire._ import fr.acinq.eclair.{ShortChannelId, UInt64} import org.json4s.JsonAST._ -import org.json4s.{CustomKeySerializer, CustomSerializer} +import org.json4s.{CustomKeySerializer, CustomSerializer, jackson} /** * JSON Serializers. @@ -144,3 +144,34 @@ class PaymentRequestSerializer extends CustomSerializer[PaymentRequest](format = JField("minFinalCltvExpiry", if (p.minFinalCltvExpiry.isDefined) JLong(p.minFinalCltvExpiry.get) else JNull) :: Nil) })) + +trait WithJsonSerializers { + + implicit val serialization = jackson.Serialization + + implicit val formats = org.json4s.DefaultFormats + + new BinaryDataSerializer + + new UInt64Serializer + + new MilliSatoshiSerializer + + new ShortChannelIdSerializer + + new StateSerializer + + new ShaChainSerializer + + new PublicKeySerializer + + new PrivateKeySerializer + + new ScalarSerializer + + new PointSerializer + + new TransactionSerializer + + new TransactionWithInputInfoSerializer + + new InetSocketAddressSerializer + + new OutPointSerializer + + new OutPointKeySerializer + + new InputInfoSerializer + + new ColorSerializer + + new RouteResponseSerializer + + new ThrowableSerializer + + new FailureMessageSerializer + + new NodeAddressSerializer + + new DirectionSerializer + + new PaymentRequestSerializer + +} \ No newline at end of file diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/NewService.scala b/eclair-core/src/main/scala/fr/acinq/eclair/api/NewService.scala index 6f4c16e6b..43d76c3fa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/NewService.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/api/NewService.scala @@ -8,101 +8,112 @@ import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{BinaryData, MilliSatoshi, Satoshi} import fr.acinq.eclair.{Kit, ShortChannelId} import fr.acinq.eclair.io.{NodeURI, Peer} -import org.json4s.jackson import Marshallers._ -import de.heikoseeberger.akkahttpjson4s.Json4sSupport -import fr.acinq.eclair.channel.{CMD_CLOSE, Register} +import akka.actor.ActorRef +import fr.acinq.eclair.channel._ +import fr.acinq.eclair.io.Peer.{GetPeerInfo, PeerInfo} import fr.acinq.eclair.wire.NodeAddress import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.duration._ -trait NewService { +trait NewService extends WithJsonSerializers { def appKit: Kit def getInfoResponse: Future[GetInfoResponse] implicit val ec = appKit.system.dispatcher - - implicit val serialization = jackson.Serialization - implicit val formats = org.json4s.DefaultFormats + - new BinaryDataSerializer + - new UInt64Serializer + - new MilliSatoshiSerializer + - new ShortChannelIdSerializer + - new StateSerializer + - new ShaChainSerializer + - new PublicKeySerializer + - new PrivateKeySerializer + - new ScalarSerializer + - new PointSerializer + - new TransactionSerializer + - new TransactionWithInputInfoSerializer + - new InetSocketAddressSerializer + - new OutPointSerializer + - new OutPointKeySerializer + - new InputInfoSerializer + - new ColorSerializer + - new RouteResponseSerializer + - new ThrowableSerializer + - new FailureMessageSerializer + - new NodeAddressSerializer + - new DirectionSerializer + - new PaymentRequestSerializer - implicit val timeout = Timeout(60 seconds) implicit val shouldWritePretty: ShouldWritePretty = ShouldWritePretty.True - import Json4sSupport.{marshaller, unmarshaller} - val connectRoute: Route = path("connect") { - parameters("nodeId".as[PublicKey], "address".as[NodeAddress]) { (nodeId, addr) => - connect(s"$nodeId@$addr") - } ~ parameters("uri") { uri => - connect(uri) - } - } + val channelIdNamedParameter = "channelId".as[PublicKey] - val openRoute: Route = path("open") { - parameters("nodeId".as[PublicKey], "fundingSatoshis".as[Long], "pushMsat".as[Long].?, "fundingFeerateSatByte".as[Long].?, "channelFlags".as[Int].?) { - (nodeId, fundingSatoshis, pushMsat, fundingFeerateSatByte, channelFlags) => - open(nodeId, fundingSatoshis, pushMsat, fundingFeerateSatByte, channelFlags) - } - } - - val closeRoute: Route = path("close") { - parameters("channelId".as[BinaryData](sha256HashUnmarshaller), "scriptPubKey".as[BinaryData](binaryDataUnmarshaller).?) { (channelId: BinaryData, scriptPubKey_opt: Option[BinaryData]) => - close(channelId.toString(), scriptPubKey_opt) - } - } - - val getInfoRoute: Route = path("getinfo") { - complete(getInfoResponse) - } - - val motherRoute: Route = { + val route: Route = { get { - connectRoute ~ - openRoute ~ - closeRoute ~ - getInfoRoute + path("getinfo") { + complete(getInfoResponse) + } ~ + path("help") { + complete(help.mkString) + } + path("connect") { + parameters("nodeId".as[PublicKey], "address".as[NodeAddress]) { (nodeId, addr) => + complete(connect(s"$nodeId@$addr")) + } ~ parameters("uri") { uri => + complete(connect(uri)) + } + } ~ + path("open") { + parameters("nodeId".as[PublicKey], "fundingSatoshis".as[Long], "pushMsat".as[Long].?, "fundingFeerateSatByte".as[Long].?, "channelFlags".as[Int].?) { + (nodeId, fundingSatoshis, pushMsat, fundingFeerateSatByte, channelFlags) => + complete(open(nodeId, fundingSatoshis, pushMsat, fundingFeerateSatByte, channelFlags)) + } + } ~ + path("close") { + parameters(channelIdNamedParameter, "scriptPubKey".as[BinaryData](binaryDataUnmarshaller).?) { (channelId, scriptPubKey_opt) => + complete(close(channelId.toString(), scriptPubKey_opt)) + } + } ~ + path("forceclose") { + parameters(channelIdNamedParameter) { channelId => + complete(forceClose(channelId.toString)) + } + } ~ + path("updaterelayfee") { + parameters(channelIdNamedParameter, "feeBaseMsat".as[Long], "feeProportionalMillionths".as[Long]) { (channelId, feeBase, feeProportional) => + complete(updateRelayFee(channelId.toString, feeBase, feeProportional)) + } + } ~ + path("peers") { + complete(peersInfo()) + } ~ + path("channels") { + parameters(channelIdNamedParameter.?) { channelId_opt => + complete(channels(channelId_opt)) + } + } } } - def connect(uri: String) : Route = { - complete((appKit.switchboard ? Peer.Connect(NodeURI.parse(uri))).mapTo[String]) + def connect(uri: String): Future[String] = { + (appKit.switchboard ? Peer.Connect(NodeURI.parse(uri))).mapTo[String] } - def open(nodeId: PublicKey, fundingSatoshis: Long, pushMsat: Option[Long], fundingFeerateSatByte: Option[Long], flags: Option[Int]): Route = { - complete((appKit.switchboard ? Peer.OpenChannel( + def open(nodeId: PublicKey, fundingSatoshis: Long, pushMsat: Option[Long], fundingFeerateSatByte: Option[Long], flags: Option[Int]): Future[String] = { + (appKit.switchboard ? Peer.OpenChannel( remoteNodeId = nodeId, fundingSatoshis = Satoshi(fundingSatoshis), pushMsat = pushMsat.map(MilliSatoshi).getOrElse(MilliSatoshi(0)), fundingTxFeeratePerKw_opt = fundingFeerateSatByte, - channelFlags = flags.map(_.toByte))).mapTo[String]) + channelFlags = flags.map(_.toByte))).mapTo[String] } - def close(channelId: String, scriptPubKey: Option[BinaryData]) = { - complete(sendToChannel(channelId, CMD_CLOSE(scriptPubKey)).mapTo[String]) + def close(channelId: String, scriptPubKey: Option[BinaryData]): Future[String] = { + sendToChannel(channelId, CMD_CLOSE(scriptPubKey)).mapTo[String] + } + + def forceClose(channelId: String): Future[String] = { + sendToChannel(channelId, CMD_FORCECLOSE).mapTo[String] + } + + def updateRelayFee(channelId: String, feeBaseMsat: Long, feeProportionalMillionths: Long): Future[String] = { + sendToChannel(channelId, CMD_UPDATE_RELAY_FEE(feeBaseMsat, feeProportionalMillionths)).mapTo[String] + } + + def peersInfo(): Future[Iterable[PeerInfo]] = for { + peers <- (appKit.switchboard ? 'peers).mapTo[Iterable[ActorRef]] + peerinfos <- Future.sequence(peers.map(peer => (peer ? GetPeerInfo).mapTo[PeerInfo])) + } yield peerinfos + + def channels(channelIdFilter: Option[PublicKey]): Future[Iterable[RES_GETINFO]] = channelIdFilter match { + case Some(pk) => for { + channelsId <- (appKit.register ? 'channelsTo).mapTo[Map[BinaryData, PublicKey]].map(_.filter(_._2 == pk).keys) + channels <- Future.sequence(channelsId.map(channelId => sendToChannel(channelId.toString(), CMD_GETINFO).mapTo[RES_GETINFO])) + } yield channels + case None => for { + channels_id <- (appKit.register ? 'channels).mapTo[Map[BinaryData, ActorRef]].map(_.keys) + channels <- Future.sequence(channels_id.map(channel_id => sendToChannel(channel_id.toString(), CMD_GETINFO).mapTo[RES_GETINFO])) + } yield channels } /** @@ -120,4 +131,39 @@ trait NewService { res <- appKit.register ? fwdReq } yield res + def help = List( + "connect (uri): open a secure connection to a lightning node", + "connect (nodeId, host, port): open a secure connection to a lightning node", + "open (nodeId, fundingSatoshis, pushMsat = 0, feerateSatPerByte = ?, channelFlags = 0x01): open a channel with another lightning node, by default push = 0, feerate for the funding tx targets 6 blocks, and channel is announced", + "updaterelayfee (channelId, feeBaseMsat, feeProportionalMillionths): update relay fee for payments going through this channel", + "peers: list existing local peers", + "channels: list existing local channels", + "channels (nodeId): list existing local channels to a particular nodeId", + "channel (channelId): retrieve detailed information about a given channel", + "channelstats: retrieves statistics about channel usage (fees, number and average amount of payments)", + "allnodes: list all known nodes", + "allchannels: list all known channels", + "allupdates: list all channels updates", + "allupdates (nodeId): list all channels updates for this nodeId", + "receive (amountMsat, description): generate a payment request for a given amount", + "receive (amountMsat, description, expirySeconds): generate a payment request for a given amount with a description and a number of seconds till it expires", + "parseinvoice (paymentRequest): returns node, amount and payment hash in a payment request", + "findroute (paymentRequest): returns nodes and channels of the route if there is any", + "findroute (paymentRequest, amountMsat): returns nodes and channels of the route if there is any", + "findroute (nodeId, amountMsat): returns nodes and channels of the route if there is any", + "send (amountMsat, paymentHash, nodeId): send a payment to a lightning node", + "send (paymentRequest): send a payment to a lightning node using a BOLT11 payment request", + "send (paymentRequest, amountMsat): send a payment to a lightning node using a BOLT11 payment request and a custom amount", + "close (channelId): close a channel", + "close (channelId, scriptPubKey): close a channel and send the funds to the given scriptPubKey", + "forceclose (channelId): force-close a channel by publishing the local commitment tx (careful: this is more expensive than a regular close and will incur a delay before funds are spendable)", + "checkpayment (paymentHash): returns true if the payment has been received, false otherwise", + "checkpayment (paymentRequest): returns true if the payment has been received, false otherwise", + "audit: list all send/received/relayed payments", + "audit (from, to): list send/received/relayed payments in that interval (from <= timestamp < to)", + "networkfees: list all network fees paid to the miners, by transaction", + "networkfees (from, to): list network fees paid to the miners, by transaction, in that interval (from <= timestamp < to)", + "getinfo: returns info about the blockchain and this node", + "help: display this message") + } \ No newline at end of file