Skip to content

Commit

Permalink
Adding ability to set expiry when creating a new invoice (#632)
Browse files Browse the repository at this point in the history
  • Loading branch information
n1bor authored and pm47 committed Jun 19, 2018
1 parent 5afd9fa commit b3731ad
Show file tree
Hide file tree
Showing 6 changed files with 22 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ java -Declair.datadir=/tmp/node1 -jar eclair-node-gui-<version>-<commit_id>.jar
allupdates | nodeId | list all channels updates for this nodeId
receive | description | generate a payment request without a required amount (can be useful for donations)
receive | amountMsat, description | generate a payment request for a given amount
receive | amountMsat, description, expirySeconds | generate a payment request for a given amount that expires after given number of seconds
checkinvoice | paymentRequest | returns node, amount and payment hash in an invoice/paymentRequest
findroute | paymentRequest|nodeId | given a payment request or nodeID checks if there is a valid payment route returns JSON with attempts, nodes and channels of route
send | amountMsat, paymentHash, nodeId | send a payment to a lightning node
Expand Down
2 changes: 1 addition & 1 deletion eclair-core/eclair-cli
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/bin/bash

# Check if jq is installed. If not, display instructions and abort program
command -v jq >/dev/null 2>&1 || { echo -e "This tool requires jq.\nFor installation instructions, visit https://stedolan.github.io/jq/download/.\n\nAborting..."; exit 1; }

Expand Down Expand Up @@ -79,6 +78,7 @@ case ${METHOD}_${#} in
"open_2") call ${METHOD} "'$(printf '["%s",%s]' "${1}" "${2}")'" ;; # ${2} is numeric (funding)

"receive_2") call ${METHOD} "'$(printf '[%s,"%s"]' "${1}" "${2}")'" ;; # ${1} is numeric (amount to receive)
"receive_3") call ${METHOD} "'$(printf '[%s,"%s",%s]' "${1}" "${2}" "${3}")'" ;; # ${1} is numeric (amount to receive) as is ${2} for expiry in seconds

"channel_"*) call ${METHOD} "'${PARAMS}'" "if .error != null then .error.message else .result | { nodeId, shortChannelId: .data.shortChannelId, channelId, state, balanceMsat: .data.commitments.localCommit.spec.toLocalMsat, capacitySat: .data.commitments.commitInput.txOut.amount.amount } end" ;;

Expand Down
5 changes: 4 additions & 1 deletion eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,9 @@ trait Service extends Logging {
// 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 JInt(amountMsat) :: JString(description) :: JInt(expirySeconds) :: Nil =>
completeRpcFuture(req.id, (paymentHandler ? ReceivePayment(Some(MilliSatoshi(amountMsat.toLong)), description, Some(expirySeconds.toLong))).mapTo[PaymentRequest].map(PaymentRequest.write))
case _ => reject(UnknownParamsRejection(req.id, "[description] or [amount, description] or [amount, description, expiryDuration]"))
}

case "checkinvoice" => req.params match {
Expand Down Expand Up @@ -346,6 +348,7 @@ trait Service extends Logging {
"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",
"checkinvoice (paymentRequest): returns node, amount and payment hash in an invoice/paymentRequest",
"findroute (paymentRequest|nodeId): given a payment request or nodeID checks if there is a valid payment route returns JSON with attempts, nodes and channels of route",
"send (amountMsat, paymentHash, nodeId): send a payment to a lightning node",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,15 @@ class LocalPaymentHandler(nodeParams: NodeParams)(implicit ec: ExecutionContext
case e@(_, (_, pr)) if pr.timestamp + pr.expiry.get > currentSeconds => e // clean up expired requests
}))

case ReceivePayment(amount_opt, desc) =>
case ReceivePayment(amount_opt, desc, expirySeconds_opt) =>
Try {
if (hash2preimage.size > nodeParams.maxPendingPaymentRequests) {
throw new RuntimeException(s"too many pending payment requests (max=${nodeParams.maxPendingPaymentRequests})")
}
val paymentPreimage = randomBytes(32)
val paymentHash = Crypto.sha256(paymentPreimage)
val paymentRequest = PaymentRequest(nodeParams.chainHash, amount_opt, paymentHash, nodeParams.privateKey, desc, fallbackAddress = None, expirySeconds = Some(nodeParams.paymentRequestExpiry.toSeconds))
val expirySeconds = expirySeconds_opt.getOrElse(nodeParams.paymentRequestExpiry.toSeconds)
val paymentRequest = PaymentRequest(nodeParams.chainHash, amount_opt, paymentHash, nodeParams.privateKey, desc, fallbackAddress = None, expirySeconds = Some(expirySeconds))
log.debug(s"generated payment request=${PaymentRequest.write(paymentRequest)} from amount=$amount_opt")
sender ! paymentRequest
context.become(run(hash2preimage + (paymentHash -> (paymentPreimage, paymentRequest))))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import fr.acinq.eclair.wire._
import scodec.Attempt

import scala.util.{Failure, Success}
import scala.concurrent.duration.FiniteDuration

/**
* Created by PM on 26/08/2016.
Expand Down Expand Up @@ -182,7 +183,7 @@ object PaymentLifecycle {
def props(sourceNodeId: PublicKey, router: ActorRef, register: ActorRef) = Props(classOf[PaymentLifecycle], sourceNodeId, router, register)

// @formatter:off
case class ReceivePayment(amountMsat_opt: Option[MilliSatoshi], description: String)
case class ReceivePayment(amountMsat_opt: Option[MilliSatoshi], description: String, expirySeconds_opt: Option[Long] = None)
/**
* @param finalCltvExpiry by default we choose finalCltvExpiry = Channel.MIN_CLTV_EXPIRY + 1 to not have our htlc fail when a new block has just been found
* @param maxFeePct set by default to 3% as a safety measure (even if a route is found, if fee is higher than that payment won't be attempted)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,20 @@ class PaymentHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike
test("Payment request generation should succeed when the amount is not set") {
val handler = system.actorOf(LocalPaymentHandler.props(Alice.nodeParams))
val sender = TestProbe()
val eventListener = TestProbe()
system.eventStream.subscribe(eventListener.ref, classOf[PaymentReceived])

sender.send(handler, ReceivePayment(None, "This is a donation PR"))
val pr = sender.expectMsgType[PaymentRequest]
assert(pr.amount.isEmpty && pr.nodeId.toString == Alice.nodeParams.nodeId.toString)
}

test("Payment request generation should handle custom expiries or use the default otherwise") {
val handler = system.actorOf(LocalPaymentHandler.props(Alice.nodeParams))
val sender = TestProbe()

sender.send(handler, ReceivePayment(Some(MilliSatoshi(42000)), "1 coffee"))
assert(sender.expectMsgType[PaymentRequest].expiry === Some(Alice.nodeParams.paymentRequestExpiry.toSeconds))

sender.send(handler, ReceivePayment(Some(MilliSatoshi(42000)), "1 coffee with custom expiry", expirySeconds_opt = Some(60)))
assert(sender.expectMsgType[PaymentRequest].expiry === Some(60))
}
}

0 comments on commit b3731ad

Please sign in to comment.