mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-23 06:35:11 +01:00
Improve JSON RPC API error handling (#322)
Service pattern matching code visually separates each method and params to improve the code readability and maintenance. Route completion is handle on a case by case basis, for each call. This enables better error management and useful feedback to the caller. Added custom rejections to handle cases where the given rpc method or params are not found or not correct. HTTP code should now be consistent with the error returned.
This commit is contained in:
parent
6a5814d4fe
commit
059f211916
5 changed files with 195 additions and 105 deletions
|
@ -128,6 +128,7 @@ java -Declair.datadir=/tmp/node1 -jar eclair-node-gui-<version>-<commit_id>.jar
|
|||
open | nodeId, host, port, fundingSatoshis, pushMsat | opens a channel with another lightning node
|
||||
peers | | list existing local peers
|
||||
channels | | list existing local channels
|
||||
channels | nodeId | list existing local channels opened with a particular nodeId
|
||||
channel | channelId | retrieve detailed information about a given channel
|
||||
allnodes | | list all known nodes
|
||||
allchannels | | list all known channels
|
||||
|
@ -136,6 +137,8 @@ java -Declair.datadir=/tmp/node1 -jar eclair-node-gui-<version>-<commit_id>.jar
|
|||
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
|
||||
checkpayment| paymentHash | returns true if the payment has been received, false otherwise
|
||||
checkpayment| paymentRequest | returns true if the payment has been received, false otherwise
|
||||
close | channelId | close a channel
|
||||
close | channelId, scriptPubKey (optional) | close a channel and send the funds to the given scriptPubKey
|
||||
help | | display available methods
|
||||
|
|
|
@ -18,8 +18,13 @@ case $1 in
|
|||
"channels")
|
||||
eval curl "$CURL_OPTS -d '{ \"method\": \"channels\", \"params\" : [] }' $URL" | jq ".result[]"
|
||||
;;
|
||||
"channelsto")
|
||||
eval curl "$CURL_OPTS -d '{ \"method\": \"channelsto\", \"params\" : [\"${2?"missing node id"}\"] }' $URL" | jq ".result[]"
|
||||
"channels")
|
||||
if [ $# -ge 2 ]
|
||||
then
|
||||
eval curl "$CURL_OPTS -d '{ \"method\": \"channels\", \"params\" : [\"${2?"missing node id"}\"] }' $URL" | jq ".result[]"
|
||||
else
|
||||
eval curl "$CURL_OPTS -d '{ \"method\": \"channels\", \"params\" : [] }' $URL" | jq ".result[]"
|
||||
fi
|
||||
;;
|
||||
"channel")
|
||||
eval curl "$CURL_OPTS -d '{ \"method\": \"channel\", \"params\" : [\"${2?"missing channel id"}\"] }' $URL" | jq ".result | { nodeid, channelId, state, balanceMsat: .data.commitments.localCommit.spec.toLocalMsat, capacitySat: .data.commitments.commitInput.txOut.amount.amount }"
|
||||
|
@ -45,4 +50,7 @@ case $1 in
|
|||
"peers")
|
||||
eval curl "$CURL_OPTS -d '{ \"method\": \"peers\", \"params\" : [] }' $URL" | jq ".result"
|
||||
;;
|
||||
"checkpayment")
|
||||
eval curl "$CURL_OPTS -d '{ \"method\": \"checkpayment\", \"params\" : [\"${2?"missing payment request or payment hash"}\"] }' $URL" | jq ".result"
|
||||
;;
|
||||
esac
|
||||
|
|
|
@ -7,12 +7,12 @@ import scala.util.{Failure, Success, Try}
|
|||
object DBCompatChecker extends Logging {
|
||||
|
||||
/**
|
||||
* Tests if the DB files are compatible with the current version of eclair; throws an exception if incompatible.
|
||||
* Tests if the channels data in the DB are compatible with the current version of eclair; throws an exception if incompatible.
|
||||
*
|
||||
* @param nodeParams
|
||||
*/
|
||||
def checkDBCompatibility(nodeParams: NodeParams): Unit =
|
||||
Try(nodeParams.networkDb.listChannels() ++ nodeParams.networkDb.listNodes() ++ nodeParams.peersDb.listPeers() ++ nodeParams.channelsDb.listChannels()) match {
|
||||
Try(nodeParams.channelsDb.listChannels()) match {
|
||||
case Success(_) => {}
|
||||
case Failure(_) => throw IncompatibleDBException
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@ import akka.http.scaladsl.model.headers.CacheDirectives.{`max-age`, `no-store`,
|
|||
import akka.http.scaladsl.model.headers.HttpOriginRange.*
|
||||
import akka.http.scaladsl.model.headers._
|
||||
import akka.http.scaladsl.server.Directives._
|
||||
import akka.http.scaladsl.server.Route
|
||||
import akka.http.scaladsl.server.directives.RouteDirectives.reject
|
||||
import akka.http.scaladsl.server.{ExceptionHandler, Rejection, RejectionHandler, Route}
|
||||
import akka.pattern.ask
|
||||
import akka.util.Timeout
|
||||
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
|
||||
|
@ -37,6 +38,13 @@ case class JsonRPCRes(result: AnyRef, error: Option[Error], id: String)
|
|||
case class Status(node_id: String)
|
||||
case class GetInfoResponse(nodeId: PublicKey, alias: String, port: Int, chainHash: BinaryData, blockHeight: Int)
|
||||
case class ChannelInfo(shortChannelId: String, nodeId1: PublicKey, nodeId2: PublicKey)
|
||||
trait RPCRejection extends Rejection {
|
||||
def requestId: String
|
||||
}
|
||||
final case class UnknownMethodRejection(requestId: String) extends RPCRejection
|
||||
final case class UnknownParamsRejection(requestId: String, message: String) extends RPCRejection
|
||||
final case class NotFoundRejection(requestId: String) extends RPCRejection
|
||||
final case class ValidationRejection(requestId: String, message: String) extends RPCRejection
|
||||
// @formatter:on
|
||||
|
||||
trait Service extends Logging {
|
||||
|
@ -52,14 +60,182 @@ trait Service extends Logging {
|
|||
|
||||
def appKit: Kit
|
||||
|
||||
def getInfoResponse: Future[GetInfoResponse]
|
||||
|
||||
val customHeaders = `Access-Control-Allow-Origin`(*) ::
|
||||
`Access-Control-Allow-Headers`("Content-Type, Authorization") ::
|
||||
`Access-Control-Allow-Methods`(PUT, GET, POST, DELETE, OPTIONS) ::
|
||||
`Cache-Control`(public, `no-store`, `max-age`(0)) ::
|
||||
`Access-Control-Allow-Headers`("x-requested-with") :: Nil
|
||||
|
||||
val myExceptionHandler = ExceptionHandler {
|
||||
case t: Throwable =>
|
||||
extractRequest { request =>
|
||||
logger.info(s"API call failed with cause=${t.getMessage}")
|
||||
complete(StatusCodes.InternalServerError, JsonRPCRes(null, Some(Error(StatusCodes.InternalServerError.intValue, t.getMessage)), request.asInstanceOf[JsonRPCBody].id))
|
||||
}
|
||||
}
|
||||
|
||||
def completeRpcFuture(requestId: String, future: Future[AnyRef]): Route = onComplete(future) {
|
||||
case Success(s) => completeRpc(requestId, s)
|
||||
case Failure(_) => reject
|
||||
}
|
||||
def completeRpc(requestId: String, result: AnyRef): Route = complete(JsonRPCRes(result, None, requestId))
|
||||
|
||||
val myRejectionHandler: RejectionHandler = RejectionHandler.newBuilder()
|
||||
.handleNotFound {
|
||||
complete(StatusCodes.NotFound, JsonRPCRes(null, Some(Error(StatusCodes.NotFound.intValue, "not found")), "-1"))
|
||||
}
|
||||
.handle {
|
||||
case v: ValidationRejection ⇒ complete(StatusCodes.BadRequest, JsonRPCRes(null, Some(Error(StatusCodes.BadRequest.intValue, v.message)), v.requestId))
|
||||
case nf: NotFoundRejection ⇒ complete(StatusCodes.NotFound, JsonRPCRes(null, Some(Error(StatusCodes.NotFound.intValue, "not found")), nf.requestId))
|
||||
case ukm: UnknownMethodRejection ⇒ complete(StatusCodes.BadRequest, JsonRPCRes(null, Some(Error(StatusCodes.BadRequest.intValue, "method not found")), ukm.requestId))
|
||||
case p: UnknownParamsRejection ⇒ complete(StatusCodes.BadRequest,
|
||||
JsonRPCRes(null, Some(Error(StatusCodes.BadRequest.intValue, s"invalid parameters for this method, should be: ${p.message}")), p.requestId))
|
||||
case r ⇒ logger.error(s"API call failed with cause=$r")
|
||||
complete(StatusCodes.BadRequest, JsonRPCRes(null, Some(Error(StatusCodes.BadRequest.intValue, r.toString)), "-1"))
|
||||
}
|
||||
.result()
|
||||
|
||||
val route: Route =
|
||||
respondWithDefaultHeaders(customHeaders) {
|
||||
handleExceptions(myExceptionHandler) {
|
||||
handleRejections(myRejectionHandler) {
|
||||
pathSingleSlash {
|
||||
post {
|
||||
entity(as[JsonRPCBody]) {
|
||||
req =>
|
||||
val kit = appKit
|
||||
import kit._
|
||||
|
||||
req.method match {
|
||||
// utility methods
|
||||
case "getinfo" => completeRpcFuture(req.id, getInfoResponse)
|
||||
case "help" => completeRpc(req.id, help)
|
||||
|
||||
// channel lifecycle methods
|
||||
case "connect" => req.params match {
|
||||
case JString(nodeId) :: JString(host) :: JInt(port) :: Nil =>
|
||||
completeRpcFuture(req.id, (switchboard ? NewConnection(PublicKey(nodeId), new InetSocketAddress(host, port.toInt), None)).mapTo[String])
|
||||
case _ => reject(UnknownParamsRejection(req.id, "[nodeId, host, port]"))
|
||||
}
|
||||
case "open" => req.params match {
|
||||
case JString(nodeId) :: JString(host) :: JInt(port) :: JInt(fundingSatoshi) :: JInt(pushMsat) :: JInt(newChannel) :: Nil =>
|
||||
completeRpcFuture(req.id, (switchboard ? NewConnection(PublicKey(nodeId), new InetSocketAddress(host, port.toInt), Some(NewChannel(Satoshi(fundingSatoshi.toLong),
|
||||
MilliSatoshi(pushMsat.toLong), Some(newChannel.toByte))))).mapTo[String])
|
||||
case JString(nodeId) :: JString(host) :: JInt(port) :: JInt(fundingSatoshi) :: JInt(pushMsat) :: Nil =>
|
||||
completeRpcFuture(req.id, (switchboard ? NewConnection(PublicKey(nodeId), new InetSocketAddress(host, port.toInt), Some(NewChannel(Satoshi(fundingSatoshi.toLong),
|
||||
MilliSatoshi(pushMsat.toLong), None)))).mapTo[String])
|
||||
case _ => reject(UnknownParamsRejection(req.id, "[nodeId, host, port, fundingSatoshi, pushMsat] or [nodeId, host, port, fundingSatoshi, pushMsat, newChannel]"))
|
||||
}
|
||||
case "close" => req.params match {
|
||||
case JString(identifier) :: Nil => completeRpc(req.id, sendToChannel(identifier, CMD_CLOSE(scriptPubKey = None)).mapTo[String])
|
||||
case JString(identifier) :: JString(scriptPubKey) :: Nil => completeRpc(req.id, sendToChannel(identifier, CMD_CLOSE(scriptPubKey = Some(scriptPubKey))).mapTo[String])
|
||||
case _ => reject(UnknownParamsRejection(req.id, "[channelId] or [channelId, scriptPubKey]"))
|
||||
}
|
||||
|
||||
// local network methods
|
||||
case "peers" => completeRpcFuture(req.id, (switchboard ? 'peers).mapTo[Map[PublicKey, ActorRef]].map(_.map(_._1.toBin)))
|
||||
case "channels" => req.params match {
|
||||
case Nil => completeRpcFuture(req.id, (register ? 'channels).mapTo[Map[Long, ActorRef]].map(_.keys))
|
||||
case JString(remoteNodeId) :: Nil => Try(PublicKey(remoteNodeId)) match {
|
||||
case Success(pk) => completeRpcFuture(req.id, (register ? 'channelsTo).mapTo[Map[BinaryData, PublicKey]].map(_.filter(_._2 == pk).keys))
|
||||
case Failure(f) => reject(ValidationRejection(req.id, s"invalid remote node id '$remoteNodeId'"))
|
||||
}
|
||||
case _ => reject(UnknownParamsRejection(req.id, "no arguments or [remoteNodeId]"))
|
||||
}
|
||||
case "channel" => req.params match {
|
||||
case JString(identifier) :: Nil => completeRpcFuture(req.id, sendToChannel(identifier, CMD_GETINFO).mapTo[RES_GETINFO])
|
||||
case _ => reject(UnknownParamsRejection(req.id, "[channelId]"))
|
||||
}
|
||||
|
||||
// global network methods
|
||||
case "allnodes" => completeRpcFuture(req.id, (router ? 'nodes).mapTo[Iterable[NodeAnnouncement]].map(_.map(_.nodeId)))
|
||||
case "allchannels" => completeRpcFuture(req.id, (router ? 'channels).mapTo[Iterable[ChannelAnnouncement]].map(_.map(c => ChannelInfo(c.shortChannelId.toHexString, c.nodeId1, c.nodeId2))))
|
||||
|
||||
// payment methods
|
||||
case "receive" => req.params match {
|
||||
// only the payment description is given: user may want to generate a donation payment request
|
||||
case JString(description) :: Nil =>
|
||||
completeRpcFuture(req.id, (paymentHandler ? ReceivePayment(None, description)).mapTo[PaymentRequest].map(PaymentRequest.write))
|
||||
// the amount is now given with the description
|
||||
case JInt(amountMsat) :: JString(description) :: Nil =>
|
||||
completeRpcFuture(req.id, (paymentHandler ? ReceivePayment(Some(MilliSatoshi(amountMsat.toLong)), description)).mapTo[PaymentRequest].map(PaymentRequest.write))
|
||||
case _ => reject(UnknownParamsRejection(req.id, "[description] or [amount, description]"))
|
||||
}
|
||||
case "send" => req.params match {
|
||||
// user manually sets the payment information
|
||||
case JInt(amountMsat) :: JString(paymentHash) :: JString(nodeId) :: Nil =>
|
||||
(Try(BinaryData(paymentHash)), Try(PublicKey(nodeId))) match {
|
||||
case (Success(ph), Success(pk)) => completeRpcFuture(req.id, (paymentInitiator ? SendPayment(amountMsat.toLong, ph, pk)).mapTo[PaymentResult])
|
||||
case (Failure(_), _) => reject(ValidationRejection(req.id, s"invalid payment hash '$paymentHash'"))
|
||||
case _ => reject(ValidationRejection(req.id, s"invalid node id '$nodeId'"))
|
||||
}
|
||||
// user gives a Lightning payment request
|
||||
case JString(paymentRequest) :: rest => Try(PaymentRequest.read(paymentRequest)) match {
|
||||
case Success(pr) =>
|
||||
// setting the payment amount
|
||||
val amount_msat: Long = (pr.amount, rest) match {
|
||||
// optional amount always overrides the amount in the payment request
|
||||
case (_, JInt(amount_msat_override) :: Nil) => amount_msat_override.toLong
|
||||
case (Some(amount_msat_pr), _) => amount_msat_pr.amount
|
||||
case _ => throw new RuntimeException("you must manually specify an amount for this payment request")
|
||||
}
|
||||
logger.debug(s"api call for sending payment with amount_msat=$amount_msat")
|
||||
// optional cltv expiry
|
||||
val sendPayment = pr.minFinalCltvExpiry match {
|
||||
case None => SendPayment(amount_msat, pr.paymentHash, pr.nodeId)
|
||||
case Some(minFinalCltvExpiry) => SendPayment(amount_msat, pr.paymentHash, pr.nodeId, assistedRoutes = Nil, minFinalCltvExpiry)
|
||||
}
|
||||
completeRpcFuture(req.id, (paymentInitiator ? sendPayment).mapTo[PaymentResult])
|
||||
case _ => reject(ValidationRejection(req.id, s"payment request is not valid"))
|
||||
}
|
||||
case _ => reject(UnknownParamsRejection(req.id, "[amountMsat, paymentHash, nodeId or [paymentRequest] or [paymentRequest, amountMsat]"))
|
||||
}
|
||||
|
||||
// check received payments
|
||||
case "checkpayment" => req.params match {
|
||||
case JString(identifier) :: Nil => completeRpcFuture(req.id, for {
|
||||
paymentHash <- Try(PaymentRequest.read(identifier)) match {
|
||||
case Success(pr) => Future.successful(pr.paymentHash)
|
||||
case _ => Try(BinaryData(identifier)) match {
|
||||
case Success(s) => Future.successful(s)
|
||||
case _ => Future.failed(new IllegalArgumentException("payment identifier must be a payment request or a payment hash"))
|
||||
}
|
||||
}
|
||||
found <- (paymentHandler ? CheckPayment(paymentHash)).map(found => new JBool(found.asInstanceOf[Boolean]))
|
||||
} yield found)
|
||||
case _ => reject(UnknownParamsRejection(req.id, "[paymentHash] or [paymentRequest]"))
|
||||
}
|
||||
|
||||
// method name was not found
|
||||
case _ => reject(UnknownMethodRejection(req.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getInfoResponse: Future[GetInfoResponse]
|
||||
|
||||
def help = List("connect (nodeId, host, port): connect to another lightning node through a secure connection",
|
||||
"open (nodeId, host, port, fundingSatoshi, pushMsat, channelFlags = 0x01): open a channel with another lightning node",
|
||||
"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",
|
||||
"allnodes: list all known nodes",
|
||||
"allchannels: list all known channels",
|
||||
"receive (amountMsat, description): generate a payment request for a given amount",
|
||||
"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",
|
||||
"checkpayment (paymentHash): returns true if the payment has been received, false otherwise",
|
||||
"checkpayment (paymentRequest): returns true if the payment has been received, false otherwise",
|
||||
"help: display this message")
|
||||
|
||||
/**
|
||||
* Sends a request to a channel and expects a response
|
||||
*
|
||||
|
@ -74,102 +250,4 @@ trait Service extends Logging {
|
|||
.recoverWith { case _ => Future.failed(new RuntimeException(s"invalid channel identifier '$channelIdentifier'")) }
|
||||
res <- appKit.register ? fwdReq
|
||||
} yield res
|
||||
|
||||
val route: Route =
|
||||
respondWithDefaultHeaders(customHeaders) {
|
||||
pathSingleSlash {
|
||||
post {
|
||||
entity(as[JsonRPCBody]) {
|
||||
req =>
|
||||
val kit = appKit
|
||||
import kit._
|
||||
val f_res: Future[AnyRef] = req match {
|
||||
case JsonRPCBody(_, _, "getinfo", _) => getInfoResponse
|
||||
case JsonRPCBody(_, _, "connect", JString(nodeId) :: JString(host) :: JInt(port) :: Nil) =>
|
||||
(switchboard ? NewConnection(PublicKey(nodeId), new InetSocketAddress(host, port.toInt), None)).mapTo[String]
|
||||
case JsonRPCBody(_, _, "open", JString(nodeId) :: JString(host) :: JInt(port) :: JInt(fundingSatoshi) :: JInt(pushMsat) :: options) =>
|
||||
val channelFlags = options match {
|
||||
case JInt(value) :: Nil => Some(value.toByte)
|
||||
case _ => None // TODO: too lax?
|
||||
}
|
||||
(switchboard ? NewConnection(PublicKey(nodeId), new InetSocketAddress(host, port.toInt), Some(NewChannel(Satoshi(fundingSatoshi.toLong), MilliSatoshi(pushMsat.toLong), channelFlags)))).mapTo[String]
|
||||
case JsonRPCBody(_, _, "peers", _) =>
|
||||
(switchboard ? 'peers).mapTo[Map[PublicKey, ActorRef]].map(_.map(_._1.toBin))
|
||||
case JsonRPCBody(_, _, "channels", _) =>
|
||||
(register ? 'channels).mapTo[Map[Long, ActorRef]].map(_.keys)
|
||||
case JsonRPCBody(_, _, "channelsto", JString(remoteNodeId) :: Nil) =>
|
||||
val remotePubKey = Try(PublicKey(remoteNodeId)).getOrElse(throw new RuntimeException(s"invalid remote node id '$remoteNodeId'"))
|
||||
(register ? 'channelsTo).mapTo[Map[BinaryData, PublicKey]].map(_.filter(_._2 == remotePubKey).keys)
|
||||
case JsonRPCBody(_, _, "channel", JString(identifier) :: Nil) =>
|
||||
sendToChannel(identifier, CMD_GETINFO).mapTo[RES_GETINFO]
|
||||
case JsonRPCBody(_, _, "allnodes", _) =>
|
||||
(router ? 'nodes).mapTo[Iterable[NodeAnnouncement]].map(_.map(_.nodeId))
|
||||
case JsonRPCBody(_, _, "allchannels", _) =>
|
||||
(router ? 'channels).mapTo[Iterable[ChannelAnnouncement]].map(_.map(c => ChannelInfo(c.shortChannelId.toHexString, c.nodeId1, c.nodeId2)))
|
||||
case JsonRPCBody(_, _, "receive", JString(description) :: Nil) =>
|
||||
(paymentHandler ? ReceivePayment(None, description)).mapTo[PaymentRequest].map(PaymentRequest.write)
|
||||
case JsonRPCBody(_, _, "receive", JInt(amountMsat) :: JString(description) :: Nil) =>
|
||||
(paymentHandler ? ReceivePayment(Some(MilliSatoshi(amountMsat.toLong)), description)).mapTo[PaymentRequest].map(PaymentRequest.write)
|
||||
case JsonRPCBody(_, _, "send", JInt(amountMsat) :: JString(paymentHash) :: JString(nodeId) :: Nil) =>
|
||||
(paymentInitiator ? SendPayment(amountMsat.toLong, paymentHash, PublicKey(nodeId))).mapTo[PaymentResult]
|
||||
case JsonRPCBody(_, _, "send", JString(paymentRequest) :: rest) =>
|
||||
for {
|
||||
req <- Future(PaymentRequest.read(paymentRequest))
|
||||
amountMsat = (req.amount, rest) match {
|
||||
case (Some(_), JInt(amt) :: Nil) => amt.toLong // overriding payment request amount with the one provided
|
||||
case (Some(amt), _) => amt.amount
|
||||
case (None, JInt(amt) :: Nil) => amt.toLong // amount wasn't specified in request, using custom one
|
||||
case (None, _) => throw new RuntimeException("you need to manually specify an amount for this payment request")
|
||||
}
|
||||
sendPayment = req.minFinalCltvExpiry match {
|
||||
case None => SendPayment(amountMsat, req.paymentHash, req.nodeId, req.routingInfo())
|
||||
case Some(minFinalCltvExpiry) => SendPayment(amountMsat, req.paymentHash, req.nodeId, req.routingInfo(), minFinalCltvExpiry = minFinalCltvExpiry)
|
||||
}
|
||||
res <- (paymentInitiator ? sendPayment).mapTo[PaymentResult]
|
||||
} yield res
|
||||
case JsonRPCBody(_, _, "close", JString(identifier) :: JString(scriptPubKey) :: Nil) =>
|
||||
sendToChannel(identifier, CMD_CLOSE(scriptPubKey = Some(scriptPubKey))).mapTo[String]
|
||||
case JsonRPCBody(_, _, "close", JString(identifier) :: Nil) =>
|
||||
sendToChannel(identifier, CMD_CLOSE(scriptPubKey = None)).mapTo[String]
|
||||
case JsonRPCBody(_, _, "checkpayment", JString(identifier) :: Nil) =>
|
||||
for {
|
||||
paymentHash <- Try(PaymentRequest.read(identifier)) match {
|
||||
case Success(pr) => Future.successful(pr.paymentHash)
|
||||
case _ => Try(BinaryData(identifier)) match {
|
||||
case Success(s) => Future.successful(s)
|
||||
case _ => Future.failed(new IllegalArgumentException("payment identifier must be a payment request or a payment hash"))
|
||||
}
|
||||
}
|
||||
found <- (paymentHandler ? CheckPayment(paymentHash)).map(found => new JBool(found.asInstanceOf[Boolean]))
|
||||
} yield found
|
||||
case JsonRPCBody(_, _, "help", _) =>
|
||||
Future.successful(List(
|
||||
"connect (nodeId, host, port): connect to another lightning node through a secure connection",
|
||||
"open (nodeId, host, port, fundingSatoshi, pushMsat, channelFlags = 0x01): open a channel with another lightning node",
|
||||
"peers: list existing local peers",
|
||||
"channels: list existing local channels",
|
||||
"channelsto (nodeId): list existing local channels to a particular nodeId",
|
||||
"channel (channelId): retrieve detailed information about a given channel",
|
||||
"allnodes: list all known nodes",
|
||||
"allchannels: list all known channels",
|
||||
"receive (amountMsat, description): generate a payment request for a given amount",
|
||||
"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",
|
||||
"checkpayment (paymentHash): returns true if the payment has been received, false otherwise",
|
||||
"checkpayment (paymentRequest): returns true if the payment has been received, false otherwise",
|
||||
"help: display this message"))
|
||||
case _ => Future.failed(new RuntimeException("method not found"))
|
||||
}
|
||||
|
||||
onComplete(f_res) {
|
||||
case Success(res) => complete(JsonRPCRes(res, None, req.id))
|
||||
case Failure(t) => complete(StatusCodes.InternalServerError, JsonRPCRes(null, Some(Error(-1, t.getMessage)), req.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
1
pom.xml
1
pom.xml
|
@ -106,6 +106,7 @@
|
|||
<arg>-language:postfixOps</arg>
|
||||
<arg>-language:implicitConversions</arg>
|
||||
<arg>-Xfatal-warnings</arg>
|
||||
<arg>-unchecked</arg>
|
||||
</args>
|
||||
<scalaCompatVersion>${scala.version.short}</scalaCompatVersion>
|
||||
</configuration>
|
||||
|
|
Loading…
Add table
Reference in a new issue