mirror of
https://github.com/ACINQ/eclair.git
synced 2025-03-13 19:37:35 +01:00
handle min pay-to-open in Phoenix
This commit is contained in:
parent
0b3ba78c7f
commit
b59f9d640b
6 changed files with 115 additions and 32 deletions
|
@ -110,6 +110,11 @@ object PaymentReceived {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* We may reject an incoming payment, because it requires the creation of a new channel, but the amount is too low.
|
||||
*/
|
||||
case class MissedPayToOpenPayment(paymentHash: ByteVector32, amount: MilliSatoshi, minAmount: MilliSatoshi)
|
||||
|
||||
case class PaymentSettlingOnChain(id: UUID, amount: MilliSatoshi, paymentHash: ByteVector32, timestamp: Long = System.currentTimeMillis) extends PaymentEvent
|
||||
|
||||
sealed trait PaymentFailure {
|
||||
|
|
|
@ -16,18 +16,17 @@
|
|||
|
||||
package fr.acinq.eclair.payment.receive
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import akka.actor.{ActorRef, Props}
|
||||
import akka.event.Logging.MDC
|
||||
import fr.acinq.bitcoin.ByteVector32
|
||||
import fr.acinq.eclair.channel.ChannelCommandResponse
|
||||
import fr.acinq.eclair.payment.MissedPayToOpenPayment
|
||||
import fr.acinq.eclair.payment.Monitoring.{Metrics, Tags}
|
||||
import fr.acinq.eclair.wire.{FailureMessage, IncorrectOrUnknownPaymentDetails, PayToOpenRequest, UpdateAddHtlc}
|
||||
import fr.acinq.eclair.{FSMDiagnosticActorLogging, Logs, MilliSatoshi, NodeParams, wire}
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
import scala.collection.immutable.Queue
|
||||
import scala.compat.Platform
|
||||
|
||||
/**
|
||||
* Created by t-bast on 18/07/2019.
|
||||
|
@ -61,7 +60,23 @@ class MultiPartPaymentFSM(nodeParams: NodeParams, paymentHash: ByteVector32, tot
|
|||
log.warning("multi-part payment total amount mismatch: previously {}, now {}", totalAmount, part.totalAmount)
|
||||
goto(PAYMENT_FAILED) using PaymentFailed(IncorrectOrUnknownPaymentDetails(part.totalAmount, nodeParams.currentBlockHeight), updatedParts)
|
||||
} else if (d.paidAmount + part.amount >= totalAmount) {
|
||||
goto(PAYMENT_SUCCEEDED) using PaymentSucceeded(updatedParts)
|
||||
// Because of the cost of opening a new channel, there is a minimum amount for incoming payments to trigger
|
||||
// a pay-to-open. Given that the total amount of a payment is included in each payment part, we could have
|
||||
// rejected pay-to-open parts as they arrived, but it would have caused two issues:
|
||||
// - in case there is a mix of htlc parts and pay-to-open parts, the htlc parts would have been accepted and we
|
||||
// would have waited for a timeout before failing them (since the payment would never complete)
|
||||
// - if we rejected each pay-to-open part individually, we wouldn't have been able to emit a single event
|
||||
// regarding the failed pay-to-open
|
||||
// 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))
|
||||
goto(PAYMENT_FAILED) using PaymentFailed(IncorrectOrUnknownPaymentDetails(part.totalAmount, nodeParams.currentBlockHeight), updatedParts)
|
||||
case _ =>
|
||||
goto(PAYMENT_SUCCEEDED) using PaymentSucceeded(updatedParts)
|
||||
}
|
||||
} else {
|
||||
stay using d.copy(parts = updatedParts)
|
||||
}
|
||||
|
|
|
@ -303,7 +303,8 @@ object LightningMessageCodecs {
|
|||
("feeThresholdSatoshis" | satoshi.unit(Satoshi(0))) ::
|
||||
("feeProportionalMillionths" | uint32.unit(0)) ::
|
||||
("expireAt" | uint32) ::
|
||||
("htlc_opt" | optional(bool(8), updateAddHtlcCodec))).as[PayToOpenRequest]
|
||||
("htlc_opt" | optional(bool(8), updateAddHtlcCodec)) ::
|
||||
("payToOpenMinAmount" | millisatoshi)).as[PayToOpenRequest]
|
||||
|
||||
val payToOpenResponseCodec: Codec[PayToOpenResponse] = (
|
||||
("chainHash" | bytes32) ::
|
||||
|
|
|
@ -303,7 +303,8 @@ case class PayToOpenRequest(chainHash: ByteVector32,
|
|||
payToOpenFee: Satoshi,
|
||||
paymentHash: ByteVector32,
|
||||
expireAt: Long,
|
||||
htlc_opt: Option[UpdateAddHtlc]
|
||||
htlc_opt: Option[UpdateAddHtlc],
|
||||
payToOpenMinAmount: MilliSatoshi
|
||||
) extends LightningMessage with HasChainHash {
|
||||
def denied(nodeSecret: PrivateKey, failure_opt: Option[FailureMessage]): PayToOpenResponse = {
|
||||
// if we have the necessary information, we include a properly onion-encrypted failure reason
|
||||
|
@ -329,13 +330,15 @@ object PayToOpenRequest {
|
|||
require(requests.nonEmpty, "there needs to be at least one pay-to-open request")
|
||||
require(requests.map(_.chainHash).toSet.size == 1, "all pay-to-open chain hash must be equal")
|
||||
require(requests.map(_.paymentHash).toSet.size == 1, "all pay-to-open payment hash must be equal")
|
||||
require(requests.map(_.payToOpenMinAmount).toSet.size == 1, "all pay-to-open min amount must be equal")
|
||||
val chainHash = requests.head.chainHash
|
||||
val paymentHash = requests.head.paymentHash
|
||||
val totalAmount = requests.map(_.amountMsat).sum
|
||||
val payToOpenFees = requests.map(_.payToOpenFee).sum
|
||||
val fundingAmount = PayToOpenRequest.computeFunding(totalAmount, payToOpenFees)
|
||||
val expireAt = requests.map(_.expireAt).min // the aggregate request expires when the first of the underlying request expires
|
||||
PayToOpenRequest(chainHash, fundingAmount, totalAmount, payToOpenFees, paymentHash, expireAt, None)
|
||||
val payToOpenMinAmount = requests.head.payToOpenMinAmount
|
||||
PayToOpenRequest(chainHash, fundingAmount, totalAmount, payToOpenFees, paymentHash, expireAt, None, payToOpenMinAmount)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -505,7 +505,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
f.sender.send(handler, ReceivePayment(Some(1000 msat), "1 fast coffee"))
|
||||
val pr = f.sender.expectMsgType[PaymentRequest]
|
||||
|
||||
val p1 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, 1 mbtc, 1000 msat, 0 sat, pr.paymentHash, secondsFromNow(60), None)
|
||||
val p1 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, 1 mbtc, 1000 msat, 0 sat, pr.paymentHash, secondsFromNow(60), None, 0.msat)
|
||||
f.sender.send(handler, p1)
|
||||
|
||||
val r1 = f.sender.expectMsgType[PayToOpenResponse]
|
||||
|
@ -522,7 +522,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
})
|
||||
|
||||
// Extraneous pay-to-opens will be ignored
|
||||
val pExtra = PayToOpenRequest(Block.RegtestGenesisBlock.hash, 1 mbtc, 200 msat, 0 sat, pr.paymentHash, secondsFromNow(60), None)
|
||||
val pExtra = PayToOpenRequest(Block.RegtestGenesisBlock.hash, 1 mbtc, 200 msat, 0 sat, pr.paymentHash, secondsFromNow(60), None, 0.msat)
|
||||
f.sender.send(handler, MultiPartPaymentFSM.ExtraPaymentReceived(pr.paymentHash, PayToOpenPart(1000 msat, pExtra, f.sender.ref), None))
|
||||
f.sender.expectNoMsg()
|
||||
|
||||
|
@ -543,7 +543,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
f.sender.send(handler, ReceivePayment(Some(20000000 msat), "1 fast coffee"))
|
||||
val pr = f.sender.expectMsgType[PaymentRequest]
|
||||
|
||||
val p1 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding, amount, fee, pr.paymentHash, secondsFromNow(60), None)
|
||||
val p1 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding, amount, fee, pr.paymentHash, secondsFromNow(60), None, 0.msat)
|
||||
f.sender.send(handler, p1)
|
||||
|
||||
val e1 = eventListener.expectMsgType[PayToOpenRequestEvent]
|
||||
|
@ -581,7 +581,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
f.sender.send(handler, ReceivePayment(Some(20000000 msat), "1 fast coffee"))
|
||||
val pr = f.sender.expectMsgType[PaymentRequest]
|
||||
|
||||
val p1 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding, amount, fee, pr.paymentHash, secondsFromNow(60), None)
|
||||
val p1 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding, amount, fee, pr.paymentHash, secondsFromNow(60), None, 0.msat)
|
||||
f.sender.send(handler, p1)
|
||||
|
||||
val e1 = eventListener.expectMsgType[PayToOpenRequestEvent]
|
||||
|
@ -609,7 +609,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
f.sender.send(handler, ReceivePayment(Some(20000000 msat), "1 fast coffee"))
|
||||
val pr = f.sender.expectMsgType[PaymentRequest]
|
||||
|
||||
val p1 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding, amount, fee, pr.paymentHash, secondsFromNow(2), None)
|
||||
val p1 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding, amount, fee, pr.paymentHash, secondsFromNow(2), None, 0.msat)
|
||||
f.sender.send(handler, p1)
|
||||
|
||||
val e1 = eventListener.expectMsgType[PayToOpenRequestEvent]
|
||||
|
@ -624,6 +624,67 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
f.sender.expectMsgType[PendingPayments].paymentHashes.isEmpty
|
||||
}
|
||||
|
||||
test("reject single-part payment with pay-to-open if min amount not reached") { f =>
|
||||
val nodeParams = Alice.nodeParams.copy(multiPartPaymentExpiry = 500 millis, features = Features(hex"028a8a"))
|
||||
val handler = TestActorRef[PaymentHandler](PaymentHandler.props(nodeParams, f.register.ref))
|
||||
val eventListener = TestProbe()
|
||||
system.eventStream.subscribe(eventListener.ref, classOf[PayToOpenRequestEvent])
|
||||
system.eventStream.subscribe(eventListener.ref, classOf[MissedPayToOpenPayment])
|
||||
|
||||
val amount = 9000000 msat
|
||||
val fee = 1000 sat
|
||||
val funding = PayToOpenRequest.computeFunding(amount, fee)
|
||||
|
||||
f.sender.send(handler, ReceivePayment(Some(9000000 msat), "1 fast coffee"))
|
||||
val pr = f.sender.expectMsgType[PaymentRequest]
|
||||
|
||||
val p1 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding, amount, fee, pr.paymentHash, secondsFromNow(60), None, 10000000.msat)
|
||||
f.sender.send(handler, p1)
|
||||
|
||||
eventListener.expectMsg(MissedPayToOpenPayment(pr.paymentHash, amount, p1.payToOpenMinAmount))
|
||||
|
||||
val r1 = f.sender.expectMsgType[PayToOpenResponse]
|
||||
assert(r1.paymentPreimage === ByteVector32.Zeroes)
|
||||
|
||||
f.sender.send(handler, GetPendingPayments)
|
||||
f.sender.expectMsgType[PendingPayments].paymentHashes.isEmpty
|
||||
}
|
||||
|
||||
test("reject multi-part payment with pay-to-open if min amount not reached") { f =>
|
||||
val nodeParams = Alice.nodeParams.copy(multiPartPaymentExpiry = 500 millis, features = Features(hex"028a8a"))
|
||||
val handler = TestActorRef[PaymentHandler](PaymentHandler.props(nodeParams, f.register.ref))
|
||||
val eventListener = TestProbe()
|
||||
system.eventStream.subscribe(eventListener.ref, classOf[PayToOpenRequestEvent])
|
||||
system.eventStream.subscribe(eventListener.ref, classOf[MissedPayToOpenPayment])
|
||||
|
||||
f.sender.send(handler, ReceivePayment(Some(9000000 msat), "1 fast coffee"))
|
||||
val pr = f.sender.expectMsgType[PaymentRequest]
|
||||
|
||||
val add = UpdateAddHtlc(ByteVector32.One, 0, 4000000 msat, pr.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket)
|
||||
f.sender.send(handler, IncomingPacket.FinalPacket(add, Onion.createMultiPartPayload(add.amountMsat, 9000000 msat, add.cltvExpiry, pr.paymentSecret.get)))
|
||||
|
||||
val amount = 5000000 msat
|
||||
val fee = 1000 sat
|
||||
val funding = PayToOpenRequest.computeFunding(amount, fee)
|
||||
val payload = Onion.createMultiPartPayload(amount, 9000000 msat, f.defaultExpiry, pr.paymentSecret.get)
|
||||
val onion = buildOnion(Sphinx.PaymentPacket)(nodeParams.nodeId :: Nil, payload :: Nil, pr.paymentHash).packet
|
||||
val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, payload.amount, pr.paymentHash, payload.expiry, onion)
|
||||
val p1 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding, amount, fee, pr.paymentHash, secondsFromNow(45), Some(htlc), 10000000.msat)
|
||||
f.sender.send(handler, p1)
|
||||
|
||||
eventListener.expectMsg(MissedPayToOpenPayment(pr.paymentHash, 9000000 msat, p1.payToOpenMinAmount))
|
||||
|
||||
val cmd = f.register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(9000000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(nodeParams.db.payments.getIncomingPayment(pr.paymentHash).get.status === IncomingPaymentStatus.Pending)
|
||||
|
||||
val r1 = f.sender.expectMsgType[PayToOpenResponse]
|
||||
assert(r1.paymentPreimage === ByteVector32.Zeroes)
|
||||
|
||||
f.sender.send(handler, GetPendingPayments)
|
||||
f.sender.expectMsgType[PendingPayments].paymentHashes.isEmpty
|
||||
}
|
||||
|
||||
def mixPaymentSuccess(f: FixtureParam, invoiceAmount: Option[MilliSatoshi]) = {
|
||||
val nodeParams = Alice.nodeParams.copy(multiPartPaymentExpiry = 500 millis, features = featuresWithMpp)
|
||||
val handler = TestActorRef[PaymentHandler](PaymentHandler.props(nodeParams, f.register.ref))
|
||||
|
@ -645,7 +706,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val payload1 = Onion.createMultiPartPayload(amount1, 100000000 msat, CltvExpiry(420000), pr.paymentSecret.get)
|
||||
val onion1 = buildOnion(Sphinx.PaymentPacket)(nodeParams.nodeId :: Nil, payload1 :: Nil, pr.paymentHash).packet
|
||||
val htlc1 = UpdateAddHtlc(ByteVector32.Zeroes, 0, payload1.amount, pr.paymentHash, payload1.expiry, onion1)
|
||||
val p1 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding1, amount1, fee1, pr.paymentHash, secondsFromNow(45), Some(htlc1))
|
||||
val p1 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding1, amount1, fee1, pr.paymentHash, secondsFromNow(45), Some(htlc1), 0.msat)
|
||||
f.sender.send(handler, p1)
|
||||
|
||||
val amount2 = 20000000 msat
|
||||
|
@ -654,7 +715,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val payload2 = Onion.createMultiPartPayload(amount2, 100000000 msat, CltvExpiry(420000), pr.paymentSecret.get)
|
||||
val onion2 = buildOnion(Sphinx.PaymentPacket)(nodeParams.nodeId :: Nil, payload2 :: Nil, pr.paymentHash).packet
|
||||
val htlc2 = UpdateAddHtlc(ByteVector32.Zeroes, 0, payload2.amount, pr.paymentHash, payload2.expiry, onion2)
|
||||
val p2 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding2, amount2, fee2, pr.paymentHash, secondsFromNow(50), Some(htlc2))
|
||||
val p2 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding2, amount2, fee2, pr.paymentHash, secondsFromNow(50), Some(htlc2), 0.msat)
|
||||
f.sender.send(handler, p2)
|
||||
|
||||
val amount3 = 10000000 msat
|
||||
|
@ -663,7 +724,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val payload3 = Onion.createMultiPartPayload(amount3, 100000000 msat, CltvExpiry(420000), pr.paymentSecret.get)
|
||||
val onion3 = buildOnion(Sphinx.PaymentPacket)(nodeParams.nodeId :: Nil, payload3 :: Nil, pr.paymentHash).packet
|
||||
val htlc3 = UpdateAddHtlc(ByteVector32.Zeroes, 0, payload3.amount, pr.paymentHash, payload3.expiry, onion3)
|
||||
val p3 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding3, amount3, fee3, pr.paymentHash, secondsFromNow(60), Some(htlc3))
|
||||
val p3 = PayToOpenRequest(Block.RegtestGenesisBlock.hash, funding3, amount3, fee3, pr.paymentHash, secondsFromNow(60), Some(htlc3), 0.msat)
|
||||
f.sender.send(handler, p3)
|
||||
|
||||
val payToOpenAmount = amount1 + amount2 + amount3
|
||||
|
@ -678,7 +739,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
payToOpenFee = payToOpenFee,
|
||||
paymentHash = p1.paymentHash,
|
||||
expireAt = p1.expireAt,
|
||||
htlc_opt = None
|
||||
htlc_opt = None,
|
||||
payToOpenMinAmount = 0.msat
|
||||
))
|
||||
assert(e1.peer === f.sender.ref)
|
||||
e1.decision.success(true)
|
||||
|
|
|
@ -417,7 +417,7 @@ class LightningMessageCodecsSpec extends AnyFunSuite {
|
|||
assert(bin === bin2)
|
||||
}
|
||||
|
||||
test("non-reg pay-to-open") {
|
||||
test("non-reg pay-to-open (optional htlc)") {
|
||||
// we just need to make sure that old phoenix can decode new pay-to-open requests
|
||||
case class OldPayToOpenRequest(chainHash: ByteVector32,
|
||||
fundingSatoshis: Satoshi,
|
||||
|
@ -434,25 +434,22 @@ class LightningMessageCodecsSpec extends AnyFunSuite {
|
|||
("payToOpenFee" | satoshi) ::
|
||||
("paymentHash" | bytes32)).as[OldPayToOpenRequest]
|
||||
|
||||
val p = PayToOpenRequest(randomBytes32, 12 mbtc, 12345 msat, 7 sat, randomBytes32, 1234567890L, Some(UpdateAddHtlc(randomBytes32, 42, 12345 msat, randomBytes32, CltvExpiry(420), TestConstants.emptyOnionPacket)))
|
||||
val p = PayToOpenRequest(randomBytes32, 12 mbtc, 12345 msat, 7 sat, randomBytes32, 1234567890L, Some(UpdateAddHtlc(randomBytes32, 42, 12345 msat, randomBytes32, CltvExpiry(420), TestConstants.emptyOnionPacket)), 15000000 msat)
|
||||
val bits = payToOpenRequestCodec.encode(p).require
|
||||
val DecodeResult(oldp, remainder) = oldPayToOpenRequestCodec.decode(bits).require
|
||||
assert(oldp === OldPayToOpenRequest(p.chainHash, p.fundingSatoshis, p.amountMsat, p.payToOpenFee, p.paymentHash))
|
||||
assert(remainder.nonEmpty)
|
||||
}
|
||||
|
||||
test("non-reg pay-to-open 2") {
|
||||
// we just need to make sure that old phoenix can decode new pay-to-open requests
|
||||
test("non-reg pay-to-open (min amount)") {
|
||||
|
||||
case class OldPayToOpenRequest(chainHash: ByteVector32,
|
||||
fundingSatoshis: Satoshi,
|
||||
amountMsat: MilliSatoshi,
|
||||
feeSatoshis: Satoshi,
|
||||
payToOpenFee: Satoshi,
|
||||
paymentHash: ByteVector32,
|
||||
feeThresholdSatoshis: Satoshi,
|
||||
feeProportionalMillionths: Long,
|
||||
expireAt: Long,
|
||||
htlc_opt: Option[UpdateAddHtlc]
|
||||
)
|
||||
htlc_opt: Option[UpdateAddHtlc])
|
||||
|
||||
import fr.acinq.eclair.wire.CommonCodecs._
|
||||
import scodec.codecs._
|
||||
|
@ -462,16 +459,16 @@ class LightningMessageCodecsSpec extends AnyFunSuite {
|
|||
("pushMsat" | millisatoshi) ::
|
||||
("feeSatoshis" | satoshi) ::
|
||||
("paymentHash" | bytes32) ::
|
||||
("feeThresholdSatoshis" | satoshi) ::
|
||||
("feeProportionalMillionths" | uint32) ::
|
||||
("feeThresholdSatoshis" | satoshi.unit(Satoshi(0))) ::
|
||||
("feeProportionalMillionths" | uint32.unit(0)) ::
|
||||
("expireAt" | uint32) ::
|
||||
("htlc_opt" | optional(bool(8), updateAddHtlcCodec))).as[OldPayToOpenRequest]
|
||||
|
||||
val p = OldPayToOpenRequest(randomBytes32, 12 mbtc, 12345 msat, 7 sat, randomBytes32, 10000 sat, 1000, 1234567890L, Some(UpdateAddHtlc(randomBytes32, 42, 12345 msat, randomBytes32, CltvExpiry(420), TestConstants.emptyOnionPacket)))
|
||||
val bits = oldPayToOpenRequestCodec.encode(p).require
|
||||
val DecodeResult(newp, remainder) = payToOpenRequestCodec.decode(bits).require
|
||||
assert(newp === PayToOpenRequest(p.chainHash, p.fundingSatoshis, p.amountMsat, p.feeSatoshis, p.paymentHash, p.expireAt, p.htlc_opt))
|
||||
assert(remainder.isEmpty)
|
||||
val p = PayToOpenRequest(randomBytes32, 12 mbtc, 12345 msat, 7 sat, randomBytes32, 1234567890L, Some(UpdateAddHtlc(randomBytes32, 42, 12345 msat, randomBytes32, CltvExpiry(420), TestConstants.emptyOnionPacket)), 15000000 msat)
|
||||
val bits = payToOpenRequestCodec.encode(p).require
|
||||
val DecodeResult(oldp, remainder) = oldPayToOpenRequestCodec.decode(bits).require
|
||||
assert(oldp === OldPayToOpenRequest(p.chainHash, p.fundingSatoshis, p.amountMsat, p.payToOpenFee, p.paymentHash, p.expireAt, p.htlc_opt))
|
||||
assert(remainder.nonEmpty)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue