From 20ea9d0e1dd925cd2f0ad6f2d5ac810b23971e4e Mon Sep 17 00:00:00 2001 From: araspitzu Date: Wed, 3 Jul 2019 15:52:03 +0200 Subject: [PATCH] Reject payments for expired invoices (#1057) * Reject payments for expired invoices --- .../scala/fr/acinq/eclair/db/PaymentsDb.scala | 1 + .../eclair/payment/LocalPaymentHandler.scala | 2 ++ .../acinq/eclair/payment/PaymentRequest.scala | 10 ++++++++- .../eclair/payment/PaymentHandlerSpec.scala | 21 +++++++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala index 38b1f60e8..b13e19044 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala @@ -39,6 +39,7 @@ trait PaymentsDb { def getPaymentRequest(paymentHash: ByteVector32): Option[PaymentRequest] + // returns non paid payment request def getPendingPaymentRequestAndPreimage(paymentHash: ByteVector32): Option[(ByteVector32, PaymentRequest)] def listPaymentRequests(from: Long, to: Long): Seq[PaymentRequest] diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/LocalPaymentHandler.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/LocalPaymentHandler.scala index d5daaa8d1..1070f3ef3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/LocalPaymentHandler.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/LocalPaymentHandler.scala @@ -65,6 +65,8 @@ class LocalPaymentHandler(nodeParams: NodeParams) extends Actor with ActorLoggin // it must not be greater than two times the requested amount. // see https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#failure-messages paymentRequest.amount match { + case _ if paymentRequest.isExpired => + sender ! CMD_FAIL_HTLC(htlc.id, Right(IncorrectOrUnknownPaymentDetails(htlc.amountMsat)), commit = true) case _ if htlc.cltvExpiry < minFinalExpiry => sender ! CMD_FAIL_HTLC(htlc.id, Right(FinalExpiryTooSoon), commit = true) case Some(amount) if MilliSatoshi(htlc.amountMsat) < amount => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentRequest.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentRequest.scala index b35b0d7b1..b5c6da04f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentRequest.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentRequest.scala @@ -23,7 +23,8 @@ import fr.acinq.eclair.payment.PaymentRequest._ import scodec.Codec import scodec.bits.{BitVector, ByteOrdering, ByteVector} import scodec.codecs.{list, ubyte} - +import scala.concurrent.duration._ +import scala.compat.Platform import scala.util.Try /** @@ -76,6 +77,11 @@ case class PaymentRequest(prefix: String, amount: Option[MilliSatoshi], timestam case cltvExpiry: PaymentRequest.MinFinalCltvExpiry => cltvExpiry.toLong } + def isExpired: Boolean = expiry match { + case Some(expiryTime) => timestamp + expiryTime <= Platform.currentTime.milliseconds.toSeconds + case None => timestamp + DEFAULT_EXPIRY_SECONDS <= Platform.currentTime.milliseconds.toSeconds + } + /** * * @return the hash of this payment request @@ -104,6 +110,8 @@ case class PaymentRequest(prefix: String, amount: Option[MilliSatoshi], timestam object PaymentRequest { + val DEFAULT_EXPIRY_SECONDS = 3600 + val prefixes = Map( Block.RegtestGenesisBlock.hash -> "lnbcrt", Block.TestnetGenesisBlock.hash -> "lntb", diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentHandlerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentHandlerSpec.scala index 90f7c8cbf..9edf987b8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentHandlerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentHandlerSpec.scala @@ -52,6 +52,7 @@ class PaymentHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike val pr = sender.expectMsgType[PaymentRequest] assert(nodeParams.db.payments.getIncomingPayment(pr.paymentHash).isEmpty) assert(nodeParams.db.payments.getPendingPaymentRequestAndPreimage(pr.paymentHash).isDefined) + assert(!nodeParams.db.payments.getPendingPaymentRequestAndPreimage(pr.paymentHash).get._2.isExpired) val add = UpdateAddHtlc(ByteVector32(ByteVector.fill(32)(1)), 0, amountMsat.amount, pr.paymentHash, expiry, ByteVector.empty) sender.send(handler, add) @@ -149,4 +150,24 @@ class PaymentHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike sender.send(handler, ReceivePayment(Some(MilliSatoshi(42000)), "1 coffee without routing info")) assert(sender.expectMsgType[PaymentRequest].routingInfo === Nil) } + + test("LocalPaymentHandler should reject incoming payments if the payment request is expired") { + val nodeParams = Alice.nodeParams + val handler = TestActorRef[LocalPaymentHandler](LocalPaymentHandler.props(nodeParams)) + val sender = TestProbe() + val eventListener = TestProbe() + system.eventStream.subscribe(eventListener.ref, classOf[PaymentReceived]) + + val amountMsat = MilliSatoshi(42000) + val expiry = Globals.blockCount.get() + 12 + + sender.send(handler, ReceivePayment(Some(amountMsat), "some desc", expirySeconds_opt = Some(0))) + val pr = sender.expectMsgType[PaymentRequest] + + val add = UpdateAddHtlc(ByteVector32(ByteVector.fill(32)(1)), 0, amountMsat.amount, pr.paymentHash, expiry, ByteVector.empty) + sender.send(handler, add) + + sender.expectMsgType[CMD_FAIL_HTLC] + assert(nodeParams.db.payments.getIncomingPayment(pr.paymentHash).isEmpty) + } }