1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-03-12 19:01:39 +01:00

Fix pay-to-open below minimum when using MPP (#1892)

Compare the *sum* of all pay-to-open parts against the min pay-to-open
amount.
This commit is contained in:
Pierre-Marie Padiou 2021-07-19 18:55:50 +02:00 committed by GitHub
parent cca712571c
commit c22094fc2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 6 deletions

View file

@ -70,9 +70,10 @@ class MultiPartPaymentFSM(nodeParams: NodeParams, paymentHash: ByteVector32, tot
// That is why, instead, we wait for all parts to arrive. Then, if there is at least one pay-to-open part, and if
// the total received amount is less than the minimum amount required for a pay-to-open, we fail the payment.
updatedParts
.collectFirst { case p: PayToOpenPart => p } match {
case Some(p) if p.totalAmount < p.payToOpen.payToOpenMinAmount =>
context.system.eventStream.publish(MissedPayToOpenPayment(p.paymentHash, p.totalAmount, p.payToOpen.payToOpenMinAmount))
.collect { case p: PayToOpenPart => p.payToOpen } match {
case payToOpenRequests if payToOpenRequests.nonEmpty && PayToOpenRequest.combine(payToOpenRequests).amountMsat < payToOpenRequests.head.payToOpenMinAmount =>
val p = payToOpenRequests.head
context.system.eventStream.publish(MissedPayToOpenPayment(p.paymentHash, part.totalAmount, p.payToOpenMinAmount))
goto(PAYMENT_FAILED) using PaymentFailed(IncorrectOrUnknownPaymentDetails(part.totalAmount, nodeParams.currentBlockHeight), updatedParts)
case _ =>
goto(PAYMENT_SUCCEEDED) using PaymentSucceeded(updatedParts)

View file

@ -16,13 +16,14 @@
package fr.acinq.eclair.payment
import akka.actor.ActorRef
import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition}
import akka.testkit.{TestActorRef, TestProbe}
import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.{Block, ByteVector32}
import fr.acinq.eclair.payment.receive.MultiPartPaymentFSM
import fr.acinq.eclair.payment.receive.MultiPartPaymentFSM._
import fr.acinq.eclair.wire.{IncorrectOrUnknownPaymentDetails, UpdateAddHtlc}
import fr.acinq.eclair.{CltvExpiry, LongToBtcAmount, MilliSatoshi, NodeParams, TestConstants, TestKitBaseClass, randomBytes32, wire}
import fr.acinq.eclair.wire.{IncorrectOrUnknownPaymentDetails, PayToOpenRequest, UpdateAddHtlc}
import fr.acinq.eclair.{CltvExpiry, LongToBtcAmount, MilliSatoshi, NodeParams, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion, randomBytes32, wire}
import org.scalatest.funsuite.AnyFunSuiteLike
import scodec.bits.ByteVector
@ -110,6 +111,21 @@ class MultiPartPaymentFSMSpec extends TestKitBaseClass with AnyFunSuiteLike {
f.eventListener.expectNoMsg(50 millis)
}
test("fail all if total pay-to-open is below minimum") {
val f = createFixture(250 millis, 20000000 msat)
f.parent.send(f.handler, createMultiPartHtlc(20000000 msat, 16000000 msat, 1))
val payToOpenAmount = 4000000.msat
assert(payToOpenMinAmount > payToOpenAmount)
f.parent.send(f.handler, createPayToOpenPart(20000000 msat, payToOpenAmount))
val fail = f.parent.expectMsgType[MultiPartPaymentFailed]
assert(fail.paymentHash === paymentHash)
assert(fail.failure === IncorrectOrUnknownPaymentDetails(20000000 msat, f.currentBlockHeight))
assert(fail.parts.length === 2)
f.parent.expectNoMsg(50 millis)
f.eventListener.expectNoMsg(50 millis)
}
test("fulfill all when total amount reached") {
val f = createFixture(10 seconds, 1000 msat)
val parts = Seq(
@ -167,6 +183,22 @@ class MultiPartPaymentFSMSpec extends TestKitBaseClass with AnyFunSuiteLike {
f.eventListener.expectNoMsg(50 millis)
}
test("fulfill all if total pay-to-open is above minimum") {
val f = createFixture(250 millis, 20000000 msat)
val parts = Seq(
createMultiPartHtlc(20000000 msat, 6000000 msat, 1),
createPayToOpenPart(20000000 msat, 7000000 msat),
createPayToOpenPart(20000000 msat, 7000000 msat)
)
parts.foreach(p => f.parent.send(f.handler, p))
val paymentResult = f.parent.expectMsgType[MultiPartPaymentSucceeded]
assert(paymentResult.parts.toSet === parts.toSet)
f.parent.expectNoMsg(50 millis)
f.eventListener.expectNoMsg(50 millis)
}
test("receive additional htlcs after total amount reached") {
val f = createFixture(10 seconds, 1000 msat)
@ -235,4 +267,21 @@ object MultiPartPaymentFSMSpec {
HtlcPart(totalAmount, htlc)
}
val payToOpenMinAmount = 10000.sat.toMilliSatoshi
def createPayToOpenPart(totalAmount: MilliSatoshi, payToOpenAmount: MilliSatoshi): PayToOpenPart =
PayToOpenPart(
totalAmount = totalAmount,
payToOpen = PayToOpenRequest(
chainHash = Block.RegtestGenesisBlock.blockId,
fundingSatoshis = payToOpenAmount.truncateToSatoshi * 2,
amountMsat = payToOpenAmount,
payToOpenFee = 10 sat,
paymentHash = paymentHash,
expireAt = Long.MaxValue,
htlc_opt = None,
payToOpenMinAmount = payToOpenMinAmount),
peer = ActorRef.noSender
)
}