Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a proper payments database #885

Merged
merged 93 commits into from Apr 16, 2019
Merged
Changes from 91 commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
cb486df
add a payment identifier
pm47 Mar 7, 2019
34a8ff1
add a payment identifier
pm47 Mar 7, 2019
8fc4f13
Merge branch 'payments-db-extended' into payments-db
araspitzu Mar 29, 2019
61774a3
Update websocket mocks to use the Id
araspitzu Mar 29, 2019
deb8c50
Use hardcoded uuid in auditDB test
araspitzu Mar 29, 2019
46d749a
Remove InvalidPaymentHash channel exception
araspitzu Mar 29, 2019
5b38adb
Use correct serializer in test (and avoid nasty reflection access war…
araspitzu Mar 29, 2019
ec01062
Merge branch 'extended-api-pm' into payments-db
araspitzu Mar 29, 2019
29865e3
Remove unused 'close' from paymentsDb
araspitzu Apr 1, 2019
b40611d
Introduce sent_payments in PaymentDB, bump db version
araspitzu Apr 1, 2019
c2052ab
Add query functions for payment DB sentPaymentById/sentPaymentByHash
araspitzu Apr 1, 2019
4919756
Update version of db with getVersion
araspitzu Apr 1, 2019
2aeaa02
Insert or update behavior on payment_db.addSentPayment
araspitzu Apr 1, 2019
18b8d72
Add status to Outgoing payments in paymentDB
araspitzu Apr 1, 2019
dc41eff
Bring paymentDb to PaymentInitiator/PaymentLifecycle
araspitzu Apr 1, 2019
9d2b85b
Wire payment lifecycle events to paymentDB updates
araspitzu Apr 1, 2019
5fd7db5
Return the UUID of the ongoing payment in /send API
araspitzu Apr 1, 2019
3bb1482
Add api to query payments by ID
araspitzu Apr 1, 2019
a52353d
Support one option param for /audit API
araspitzu Apr 1, 2019
76891c4
Adjust integration test to deal with the UUID from the payment initiator
araspitzu Apr 2, 2019
a964f17
Merge branch 'master' into payments-db
araspitzu Apr 3, 2019
b43ec23
Revert "Remove 'fallbackAddress' from /receive API"
araspitzu Apr 3, 2019
2bce7a6
Use setVersion for db version migration, add test.
araspitzu Apr 3, 2019
f722a34
optimized imports, added license headers
pm47 Apr 4, 2019
0746396
Add test for paymentLifecycle and paymentDB
araspitzu Apr 4, 2019
e834e1b
Merge remote-tracking branch 'origin/payments-db' into payments-db
araspitzu Apr 4, 2019
b194b8e
Extract future-option directive into its own file
araspitzu Apr 4, 2019
8aea43f
Remove default UUID for CMD_ADD_HTLC, renaming
araspitzu Apr 4, 2019
8856010
Move OutgoingPaymentStatus in companion object
araspitzu Apr 4, 2019
5cf3da4
Add createdAt timestamp to OutgoingPayment
araspitzu Apr 4, 2019
9b59b7e
Renaming in PaymentsDb
araspitzu Apr 4, 2019
bd0621e
Fix compilation errors
araspitzu Apr 4, 2019
489e75a
Fix test in PaymentLifeCycleSpec
araspitzu Apr 4, 2019
f2d41ec
Expose /paymentinfo by paymentHash
araspitzu Apr 5, 2019
8e0255e
Refactor inMemory Sqlite connection in database tests
araspitzu Apr 5, 2019
9134a66
Add id column to audit.sent table, add test for db migration
araspitzu Apr 5, 2019
d05a48c
Pass NodeParams to actor props, complete javadoc for OutgoingPayment
araspitzu Apr 5, 2019
1a26906
Renaming, API and object
araspitzu Apr 5, 2019
e0f9816
Improve flaky behavior of PaymentLifecycleSpec
araspitzu Apr 5, 2019
77e1934
Update paymentDB statuses even when the lifecycle is missing.
araspitzu Apr 5, 2019
69deefe
Add invoices to payment DB
araspitzu Apr 5, 2019
6aff3ba
Add license header to ExtraDirective.scala
araspitzu Apr 9, 2019
cfdfd70
Formatting
araspitzu Apr 9, 2019
9e71507
Respond with HTTP 404 if the corresponding invoice/paymentHash was no…
araspitzu Apr 9, 2019
c83e5e2
Merge branch 'payments-db' into payment-db-with-invoices
araspitzu Apr 9, 2019
ab68bdb
Add invoices data to received_payments table
araspitzu Apr 9, 2019
8a7e39e
WIP integrating payment db in local payment handler
araspitzu Apr 9, 2019
c1ed5b9
WIP integrating payment db in local payment handler /2
araspitzu Apr 9, 2019
7c50989
Add /listinvoice and /getinvoice internal APIs
araspitzu Apr 9, 2019
c9c872e
Merge branch 'master' into payment-db-with-invoices
araspitzu Apr 9, 2019
048f356
Left-pad numeric bolt11 tagged fields to have a number of bits multip…
araspitzu Apr 9, 2019
f996132
Merge branch 'bolt11-tagged-field-padding' into payment-db-with-invoices
araspitzu Apr 10, 2019
c4d6969
WIP do not filter on expire_at when retrieving invoice+preimage
araspitzu Apr 10, 2019
c077f66
Merge branch 'master' into payments-db
araspitzu Apr 10, 2019
1514f88
Renaming, update test
araspitzu Apr 10, 2019
ea75fcb
Fix expire_at comparison, remove unused messages
araspitzu Apr 10, 2019
b33f027
Remove redundant nodeId parameter from payment life cycle
araspitzu Apr 10, 2019
29bd9e4
Use actual timestamp during test to simulate invoice expiry
araspitzu Apr 11, 2019
bcd0551
Use custom keymanager in PaymentLifecycleSpec
araspitzu Apr 11, 2019
5f56cb9
Do not migrate existing "payments" table in PaymentDB
araspitzu Apr 11, 2019
35bebab
Use VARCHAR to store payment requests
araspitzu Apr 11, 2019
7765492
Consume PaymentResult event from payment life cycle during Integratio…
araspitzu Apr 11, 2019
f48fa44
Add invoices API
araspitzu Apr 11, 2019
eebb691
Fix service endpoint concatenation, print BOLT11 serialized invoice i…
araspitzu Apr 11, 2019
6e61248
Add db test for [from, to] params in listInvoice
araspitzu Apr 11, 2019
acfb7eb
Renaming, remove CheckPayment message, update JsonSerializerSpec
araspitzu Apr 11, 2019
31db633
GUI: consume UUID reply from payment initiator
araspitzu Apr 12, 2019
22d328c
API: reply with JSON encoded response if the queried element wasn't f…
araspitzu Apr 12, 2019
c5a2ef2
Update eclair-cli help message to include new APIs
araspitzu Apr 12, 2019
8df955a
PaymentLifecycleSpec: use TestFSRef and wait until state transition b…
araspitzu Apr 12, 2019
13bb49e
Renaming, reorg of 'successfulAt/failedAt' parameters on outgoing pay…
araspitzu Apr 12, 2019
0afa57c
Return a payment request object in /receive
araspitzu Apr 12, 2019
5710e9c
renamed PaymentDb methods
pm47 Apr 12, 2019
dde1ec3
Merge remote-tracking branch 'origin/payments-db' into payments-db
araspitzu Apr 12, 2019
6fa4c22
Renaming, remove unnecessary "lazy"
araspitzu Apr 12, 2019
3de8921
Factor out common named parameters
araspitzu Apr 12, 2019
c51a09d
Rename to 'getPendingRequestAndPreimage'
araspitzu Apr 12, 2019
2ec7e29
! remove limit of pending payment requests !
araspitzu Apr 12, 2019
42ecd6b
Use seconds in timestamp field for payment events
araspitzu Apr 12, 2019
7a815ec
getRequestAndPreimage->getPendingPaymentRequestAndPreimage
pm47 Apr 12, 2019
d662ee3
removed migration, changed column types/ordering
pm47 Apr 12, 2019
df79033
Expose from/to filters in listPendingInvoices()
araspitzu Apr 12, 2019
2604800
Check condition before expecting the state transition in PaymentLifec…
araspitzu Apr 12, 2019
3e8b514
Avoid printing "null" fields when serializing an invoice to json
araspitzu Apr 15, 2019
31a1fa1
Output all outgoing payments related to a payment hash in /sentinfo
araspitzu Apr 15, 2019
6135782
Store preimage for successful sent payments, don't store successfulAt…
araspitzu Apr 15, 2019
64c94d7
Update javadoc for OutgoingPayment
araspitzu Apr 15, 2019
03125be
Rename API enpoints, reorg endpoint definitions in Service
araspitzu Apr 15, 2019
10f5aa2
Merge branch 'master' into payments-db
araspitzu Apr 15, 2019
1db4399
Finish merging master
araspitzu Apr 15, 2019
6f8fdd0
More elegant Try/Match in LocalPaymentHandler
araspitzu Apr 15, 2019
f7fc22c
Add index on paymentDb.sent_payments.payment_hash
araspitzu Apr 16, 2019
153b62c
Order results in descending order in listPaymentRequest
araspitzu Apr 16, 2019
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -30,8 +30,8 @@ and COMMAND is one of:
getinfo, connect, open, close, forceclose, updaterelayfee,
peers, channels, channel, allnodes, allchannels, allupdates,
receive, parseinvoice, findroute, findroutetonode,
send, sendtonode, checkpayment,
audit, networkfees, channelstats
payinvoice, sendtonode, getreceivedinfo, audit, networkfees,
channelstats, getsentinfo, getinvoice, allinvoice, listpendinginvoices
Examples
--------
@@ -87,7 +87,6 @@ eclair {

payment-handler = "local"
payment-request-expiry = 1 hour // default expiry for payment requests generated by this node
max-pending-payment-requests = 10000000
min-funding-satoshis = 100000
max-payment-attempts = 5

@@ -1,22 +1,38 @@
/*
* Copyright 2018 ACINQ SAS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fr.acinq.eclair

import java.util.UUID
import akka.actor.ActorRef
import akka.pattern._
import akka.util.Timeout
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{ByteVector32, MilliSatoshi, Satoshi}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.db.{NetworkFee, Stats}
import fr.acinq.eclair.db.{NetworkFee, IncomingPayment, OutgoingPayment, Stats}
import fr.acinq.eclair.io.Peer.{GetPeerInfo, PeerInfo}
import fr.acinq.eclair.io.{NodeURI, Peer}
import fr.acinq.eclair.payment.PaymentLifecycle._
import fr.acinq.eclair.payment.{PaymentLifecycle, PaymentReceived, PaymentRelayed, PaymentRequest, PaymentSent}
import fr.acinq.eclair.router.{ChannelDesc, RouteRequest, RouteResponse}
import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate, NodeAddress, NodeAnnouncement}
import scodec.bits.ByteVector

import scala.concurrent.Future
import scala.concurrent.duration._
import fr.acinq.eclair.payment.{PaymentReceived, PaymentRelayed, PaymentRequest, PaymentSent}
import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate, NodeAddress, NodeAnnouncement}

case class GetInfoResponse(nodeId: PublicKey, alias: String, chainHash: ByteVector32, blockHeight: Int, publicAddresses: Seq[NodeAddress])

@@ -34,32 +50,40 @@ trait Eclair {

def updateRelayFee(channelId: String, feeBaseMsat: Long, feeProportionalMillionths: Long): Future[String]

def peersInfo(): Future[Iterable[PeerInfo]]

def channelsInfo(toRemoteNode: Option[PublicKey]): Future[Iterable[RES_GETINFO]]

def channelInfo(channelId: ByteVector32): Future[RES_GETINFO]

def allnodes(): Future[Iterable[NodeAnnouncement]]

def allchannels(): Future[Iterable[ChannelDesc]]
def peersInfo(): Future[Iterable[PeerInfo]]

def allupdates(nodeId: Option[PublicKey]): Future[Iterable[ChannelUpdate]]
def receive(description: String, amountMsat: Option[Long], expire: Option[Long], fallbackAddress: Option[String]): Future[PaymentRequest]

def receive(description: String, amountMsat: Option[Long], expire: Option[Long]): Future[String]
def receivedInfo(paymentHash: ByteVector32): Future[Option[IncomingPayment]]

def findRoute(targetNodeId: PublicKey, amountMsat: Long, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty): Future[RouteResponse]
def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry: Option[Long] = None, maxAttempts: Option[Int] = None): Future[UUID]

def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry: Option[Long] = None, maxAttempts: Option[Int] = None): Future[PaymentResult]
def sentInfo(id: Either[UUID, ByteVector32]): Future[Seq[OutgoingPayment]]

def checkpayment(paymentHash: ByteVector32): Future[Boolean]
def findRoute(targetNodeId: PublicKey, amountMsat: Long, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty): Future[RouteResponse]

def audit(from_opt: Option[Long], to_opt: Option[Long]): Future[AuditResponse]

def networkFees(from_opt: Option[Long], to_opt: Option[Long]): Future[Seq[NetworkFee]]

def channelStats(): Future[Seq[Stats]]

def getInvoice(paymentHash: ByteVector32): Future[Option[PaymentRequest]]

def pendingInvoices(from_opt: Option[Long], to_opt: Option[Long]): Future[Seq[PaymentRequest]]

def allInvoices(from_opt: Option[Long], to_opt: Option[Long]): Future[Seq[PaymentRequest]]

def allNodes(): Future[Iterable[NodeAnnouncement]]

def allChannels(): Future[Iterable[ChannelDesc]]

def allUpdates(nodeId: Option[PublicKey]): Future[Iterable[ChannelUpdate]]

def getInfoResponse(): Future[GetInfoResponse]

}
@@ -114,41 +138,44 @@ class EclairImpl(appKit: Kit) extends Eclair {
sendToChannel(channelId.toString(), CMD_GETINFO).mapTo[RES_GETINFO]
}

override def allnodes(): Future[Iterable[NodeAnnouncement]] = (appKit.router ? 'nodes).mapTo[Iterable[NodeAnnouncement]]
override def allNodes(): Future[Iterable[NodeAnnouncement]] = (appKit.router ? 'nodes).mapTo[Iterable[NodeAnnouncement]]

override def allchannels(): Future[Iterable[ChannelDesc]] = {
override def allChannels(): Future[Iterable[ChannelDesc]] = {
(appKit.router ? 'channels).mapTo[Iterable[ChannelAnnouncement]].map(_.map(c => ChannelDesc(c.shortChannelId, c.nodeId1, c.nodeId2)))
}

override def allupdates(nodeId: Option[PublicKey]): Future[Iterable[ChannelUpdate]] = nodeId match {
override def allUpdates(nodeId: Option[PublicKey]): Future[Iterable[ChannelUpdate]] = nodeId match {
case None => (appKit.router ? 'updates).mapTo[Iterable[ChannelUpdate]]
case Some(pk) => (appKit.router ? 'updatesMap).mapTo[Map[ChannelDesc, ChannelUpdate]].map(_.filter(e => e._1.a == pk || e._1.b == pk).values)
}

override def receive(description: String, amountMsat: Option[Long], expire: Option[Long]): Future[String] = {
(appKit.paymentHandler ? ReceivePayment(description = description, amountMsat_opt = amountMsat.map(MilliSatoshi), expirySeconds_opt = expire)).mapTo[PaymentRequest].map { pr =>
PaymentRequest.write(pr)
}
override def receive(description: String, amountMsat: Option[Long], expire: Option[Long], fallbackAddress: Option[String]): Future[PaymentRequest] = {
fallbackAddress.map { fa => fr.acinq.eclair.addressToPublicKeyScript(fa, appKit.nodeParams.chainHash) } // if it's not a bitcoin address throws an exception
(appKit.paymentHandler ? ReceivePayment(description = description, amountMsat_opt = amountMsat.map(MilliSatoshi), expirySeconds_opt = expire, fallbackAddress = fallbackAddress)).mapTo[PaymentRequest]
}

override def findRoute(targetNodeId: PublicKey, amountMsat: Long, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty): Future[RouteResponse] = {
(appKit.router ? RouteRequest(appKit.nodeParams.nodeId, targetNodeId, amountMsat, assistedRoutes)).mapTo[RouteResponse]
}

override def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry_opt: Option[Long] = None, maxAttempts_opt: Option[Int] = None): Future[PaymentResult] = {
override def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry_opt: Option[Long] = None, maxAttempts_opt: Option[Int] = None): Future[UUID] = {
val maxAttempts = maxAttempts_opt.getOrElse(appKit.nodeParams.maxPaymentAttempts)
val sendPayment = minFinalCltvExpiry_opt match {
case Some(minCltv) => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, finalCltvExpiry = minCltv, maxAttempts = maxAttempts)
case None => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, maxAttempts = maxAttempts)
case None => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, maxAttempts = maxAttempts)
}
(appKit.paymentInitiator ? sendPayment).mapTo[PaymentResult].map {
case s: PaymentSucceeded => s
case f: PaymentFailed => f.copy(failures = PaymentLifecycle.transformForUser(f.failures))
(appKit.paymentInitiator ? sendPayment).mapTo[UUID]
}

override def sentInfo(id: Either[UUID, ByteVector32]): Future[Seq[OutgoingPayment]] = Future {
id match {
case Left(uuid) => appKit.nodeParams.db.payments.getOutgoingPayment(uuid).toSeq
case Right(paymentHash) => appKit.nodeParams.db.payments.getOutgoingPayments(paymentHash)
}
}

override def checkpayment(paymentHash: ByteVector32): Future[Boolean] = {
(appKit.paymentHandler ? CheckPayment(paymentHash)).mapTo[Boolean]
override def receivedInfo(paymentHash: ByteVector32): Future[Option[IncomingPayment]] = Future {
appKit.nodeParams.db.payments.getIncomingPayment(paymentHash)
}

override def audit(from_opt: Option[Long], to_opt: Option[Long]): Future[AuditResponse] = {
@@ -171,6 +198,24 @@ class EclairImpl(appKit: Kit) extends Eclair {

override def channelStats(): Future[Seq[Stats]] = Future(appKit.nodeParams.db.audit.stats)

override def allInvoices(from_opt: Option[Long], to_opt: Option[Long]): Future[Seq[PaymentRequest]] = Future {
val from = from_opt.getOrElse(0L)
val to = to_opt.getOrElse(Long.MaxValue)

appKit.nodeParams.db.payments.listPaymentRequests(from, to)
}

override def pendingInvoices(from_opt: Option[Long], to_opt: Option[Long]): Future[Seq[PaymentRequest]] = Future {
val from = from_opt.getOrElse(0L)
val to = to_opt.getOrElse(Long.MaxValue)

appKit.nodeParams.db.payments.listPendingPaymentRequests(from, to)
}

override def getInvoice(paymentHash: ByteVector32): Future[Option[PaymentRequest]] = Future {
appKit.nodeParams.db.payments.getPaymentRequest(paymentHash)
}

/**
* Sends a request to a channel and expects a response
*
@@ -188,10 +233,10 @@ class EclairImpl(appKit: Kit) extends Eclair {

override def getInfoResponse: Future[GetInfoResponse] = Future.successful(
GetInfoResponse(nodeId = appKit.nodeParams.nodeId,
alias = appKit.nodeParams.alias,
chainHash = appKit.nodeParams.chainHash,
blockHeight = Globals.blockCount.intValue(),
publicAddresses = appKit.nodeParams.publicAddresses)
alias = appKit.nodeParams.alias,
chainHash = appKit.nodeParams.chainHash,
blockHeight = Globals.blockCount.intValue(),
publicAddresses = appKit.nodeParams.publicAddresses)
)

}
@@ -73,7 +73,6 @@ case class NodeParams(keyManager: KeyManager,
channelFlags: Byte,
watcherType: WatcherType,
paymentRequestExpiry: FiniteDuration,
maxPendingPaymentRequests: Int,
minFundingSatoshis: Long,
routerConf: RouterConf,
socksProxy_opt: Option[Socks5ProxyParams],
@@ -210,7 +209,6 @@ object NodeParams {
channelFlags = config.getInt("channel-flags").toByte,
watcherType = watcherType,
paymentRequestExpiry = FiniteDuration(config.getDuration("payment-request-expiry").getSeconds, TimeUnit.SECONDS),
maxPendingPaymentRequests = config.getInt("max-pending-payment-requests"),
minFundingSatoshis = config.getLong("min-funding-satoshis"),
routerConf = RouterConf(
channelExcludeDuration = FiniteDuration(config.getDuration("router.channel-exclude-duration").getSeconds, TimeUnit.SECONDS),
@@ -245,7 +245,7 @@ class Setup(datadir: File,
authenticator = system.actorOf(SimpleSupervisor.props(Authenticator.props(nodeParams), "authenticator", SupervisorStrategy.Resume))
switchboard = system.actorOf(SimpleSupervisor.props(Switchboard.props(nodeParams, authenticator, watcher, router, relayer, wallet), "switchboard", SupervisorStrategy.Resume))
server = system.actorOf(SimpleSupervisor.props(Server.props(nodeParams, authenticator, serverBindingAddress, Some(tcpBound)), "server", SupervisorStrategy.Restart))
paymentInitiator = system.actorOf(SimpleSupervisor.props(PaymentInitiator.props(nodeParams.nodeId, router, register), "payment-initiator", SupervisorStrategy.Restart))
paymentInitiator = system.actorOf(SimpleSupervisor.props(PaymentInitiator.props(nodeParams, router, register), "payment-initiator", SupervisorStrategy.Restart))
_ = for (i <- 0 until config.getInt("autoprobe-count")) yield system.actorOf(SimpleSupervisor.props(Autoprobe.props(nodeParams, router, paymentInitiator), s"payment-autoprobe-$i", SupervisorStrategy.Restart))

kit = Kit(
@@ -0,0 +1,37 @@
/*
* Copyright 2018 ACINQ SAS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fr.acinq.eclair.api

import akka.http.scaladsl.marshalling.ToResponseMarshaller
import akka.http.scaladsl.model.{ContentTypes, HttpResponse}
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.server.{Directives, Route}
import fr.acinq.eclair.api.JsonSupport._
import scala.concurrent.{Future}
import scala.util.{Failure, Success}

trait ExtraDirectives extends Directives {

// custom directive to fail with HTTP 404 (and JSON response) if the element was not found
def completeOrNotFound[T](fut: Future[Option[T]])(implicit marshaller: ToResponseMarshaller[T]): Route = onComplete(fut) {
case Success(Some(t)) => complete(t)
case Success(None) =>
complete(HttpResponse(NotFound).withEntity(ContentTypes.`application/json`, serialization.writePretty(ErrorResponse("Not found"))))
case Failure(_) => reject
}

}
@@ -1,5 +1,23 @@
/*
* Copyright 2018 ACINQ SAS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fr.acinq.eclair.api

import java.util.UUID

import akka.http.scaladsl.unmarshalling.Unmarshaller
import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.Crypto.PublicKey
@@ -29,4 +47,8 @@ object FormParamExtractors {
ShortChannelId(str)
}

implicit val javaUUIDUnmarshaller: Unmarshaller[String, UUID] = Unmarshaller.strict { str =>
UUID.fromString(str)
}

}
@@ -17,16 +17,15 @@
package fr.acinq.eclair.api

import java.net.InetSocketAddress

import akka.http.scaladsl.model.MediaType
import akka.http.scaladsl.model.MediaTypes._
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.{Point, PrivateKey, PublicKey, Scalar}
import fr.acinq.bitcoin.{ByteVector32, MilliSatoshi, OutPoint, Transaction}
import fr.acinq.eclair.channel.State
import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.db.OutgoingPaymentStatus
import fr.acinq.eclair.payment.PaymentRequest
import fr.acinq.eclair.router.RouteResponse
import fr.acinq.eclair.transactions.Direction
@@ -139,19 +138,37 @@ class DirectionSerializer extends CustomSerializer[Direction](format => ({ null
case d: Direction => JString(d.toString)
}))

class PaymentRequestSerializer extends CustomSerializer[PaymentRequest](format => ({ null },{
case p: PaymentRequest => JObject(JField("prefix", JString(p.prefix)) ::
JField("amount", if (p.amount.isDefined) JLong(p.amount.get.toLong) else JNull) ::
JField("timestamp", JLong(p.timestamp)) ::
JField("nodeId", JString(p.nodeId.toString())) ::
JField("description", JString(p.description match {
case Left(l) => l.toString()
case Right(r) => r.toString()
})) ::
JField("paymentHash", JString(p.paymentHash.toString())) ::
JField("expiry", if (p.expiry.isDefined) JLong(p.expiry.get) else JNull) ::
JField("minFinalCltvExpiry", if (p.minFinalCltvExpiry.isDefined) JLong(p.minFinalCltvExpiry.get) else JNull) ::
Nil)
class PaymentRequestSerializer extends CustomSerializer[PaymentRequest](format => ( {
null
}, {
case p: PaymentRequest => {
val expiry = p.expiry.map(ex => JField("expiry", JLong(ex))).toSeq
val minFinalCltvExpiry = p.minFinalCltvExpiry.map(mfce => JField("minFinalCltvExpiry", JLong(mfce))).toSeq
val amount = p.amount.map(msat => JField("amount", JLong(msat.toLong))).toSeq

val fieldList = List(JField("prefix", JString(p.prefix)),
JField("timestamp", JLong(p.timestamp)),
JField("nodeId", JString(p.nodeId.toString())),
JField("serialized", JString(PaymentRequest.write(p))),
JField("description", JString(p.description match {
case Left(l) => l.toString()
case Right(r) => r.toString()
})),
JField("paymentHash", JString(p.paymentHash.toString()))) ++
expiry ++
minFinalCltvExpiry ++
amount

JObject(fieldList)
}
}))

class JavaUUIDSerializer extends CustomSerializer[UUID](format => ({ null }, {
case id: UUID => JString(id.toString)
}))

class OutgoingPaymentStatusSerializer extends CustomSerializer[OutgoingPaymentStatus.Value](format => ({ null }, {
case el: OutgoingPaymentStatus.Value => JString(el.toString)
}))

object JsonSupport extends Json4sSupport {
@@ -182,7 +199,9 @@ object JsonSupport extends Json4sSupport {
new FailureMessageSerializer +
new NodeAddressSerializer +
new DirectionSerializer +
new PaymentRequestSerializer
new PaymentRequestSerializer +
new JavaUUIDSerializer +
new OutgoingPaymentStatusSerializer

implicit val shouldWritePretty: ShouldWritePretty = ShouldWritePretty.True

@@ -307,7 +307,7 @@ trait OldService extends Logging {
case _ => Future.failed(new IllegalArgumentException("payment identifier must be a payment request or a payment hash"))
}
}
found <- (paymentHandler ? CheckPayment(ByteVector32.fromValidHex(identifier))).map(found => new JBool(found.asInstanceOf[Boolean]))
found <- Future(appKit.nodeParams.db.payments.getIncomingPayment(ByteVector32.fromValidHex(identifier)).map(_ => JBool(true)).getOrElse(JBool(false)))
} yield found)
case _ => reject(UnknownParamsRejection(req.id, "[paymentHash] or [paymentRequest]"))
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.