From 87a1d549f7c7bd465f010d80ac3f3602dce822d5 Mon Sep 17 00:00:00 2001 From: t-bast Date: Tue, 3 Sep 2024 15:07:41 +0200 Subject: [PATCH] Add `channelCreationFee` to liquidity ads Creating a new channel has an additional cost compared to adding liquidity to an existing channel: the channel will be closed in the future, which will require paying on-chain fees. Node operators can include a `channel-creation-fee-satoshis` in their liquidity ads to cover some of that future cost. --- .../acinq/lightning/channel/states/Normal.kt | 7 ++-- .../channel/states/WaitForAcceptChannel.kt | 1 + .../channel/states/WaitForOpenChannel.kt | 2 +- .../kotlin/fr/acinq/lightning/io/Peer.kt | 12 +++--- .../payment/IncomingPaymentHandler.kt | 4 +- .../fr/acinq/lightning/wire/LiquidityAds.kt | 22 +++++++---- .../channel/states/SpliceTestsCommon.kt | 16 ++++---- .../IncomingPaymentHandlerTestsCommon.kt | 6 +-- .../fr/acinq/lightning/tests/TestConstants.kt | 4 +- .../wire/LightningCodecsTestsCommon.kt | 38 +++++++++---------- .../lightning/wire/LiquidityAdsTestsCommon.kt | 17 +++++---- 11 files changed, 70 insertions(+), 59 deletions(-) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt index 2941147b5..66cb9eb4d 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt @@ -406,7 +406,7 @@ data class Normal( } Pair(this@Normal.copy(spliceStatus = SpliceStatus.None), actions) } else if (!canAffordSpliceLiquidityFees(spliceStatus.command, parentCommitment)) { - val missing = spliceStatus.command.requestRemoteFunding?.let { r -> r.fees(spliceStatus.command.feerate).total - parentCommitment.localCommit.spec.toLocal.truncateToSatoshi() } + val missing = spliceStatus.command.requestRemoteFunding?.let { r -> r.fees(spliceStatus.command.feerate, isChannelCreation = false).total - parentCommitment.localCommit.spec.toLocal.truncateToSatoshi() } logger.warning { "cannot do splice: balance is too low to pay for inbound liquidity (missing=$missing)" } spliceStatus.command.replyTo.complete(ChannelCommand.Commitment.Splice.Response.Failure.InsufficientFunds) val actions = buildList { @@ -521,6 +521,7 @@ data class Normal( Helpers.Funding.makeFundingPubKeyScript(spliceStatus.spliceInit.fundingPubkey, cmd.message.fundingPubkey), cmd.message.fundingContribution, spliceStatus.spliceInit.feerate, + isChannelCreation = false, cmd.message.willFund, )) { is Either.Left -> { @@ -858,8 +859,8 @@ data class Normal( return when (val request = splice.requestRemoteFunding) { null -> true else -> when (request.paymentDetails) { - is LiquidityAds.PaymentDetails.FromChannelBalance -> request.fees(splice.feerate).total <= parentCommitment.localCommit.spec.toLocal.truncateToSatoshi() - is LiquidityAds.PaymentDetails.FromChannelBalanceForFutureHtlc -> request.fees(splice.feerate).total <= parentCommitment.localCommit.spec.toLocal.truncateToSatoshi() + is LiquidityAds.PaymentDetails.FromChannelBalance -> request.fees(splice.feerate, isChannelCreation = false).total <= parentCommitment.localCommit.spec.toLocal.truncateToSatoshi() + is LiquidityAds.PaymentDetails.FromChannelBalanceForFutureHtlc -> request.fees(splice.feerate, isChannelCreation = false).total <= parentCommitment.localCommit.spec.toLocal.truncateToSatoshi() // Fees don't need to be paid during the splice, they will be deducted from relayed HTLCs. is LiquidityAds.PaymentDetails.FromFutureHtlc -> true is LiquidityAds.PaymentDetails.FromFutureHtlcWithPreimage -> true diff --git a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForAcceptChannel.kt b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForAcceptChannel.kt index ba64d90e6..baced6109 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForAcceptChannel.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForAcceptChannel.kt @@ -58,6 +58,7 @@ data class WaitForAcceptChannel( fundingParams.fundingPubkeyScript(channelKeys), accept.fundingAmount, lastSent.fundingFeerate, + isChannelCreation = true, accept.willFund )) { is Either.Left -> { diff --git a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForOpenChannel.kt b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForOpenChannel.kt index e103af1e7..ed30eabbb 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForOpenChannel.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForOpenChannel.kt @@ -48,7 +48,7 @@ data class WaitForOpenChannel( fundingRates == null -> null requestFunding == null -> null requestFunding.requestedAmount > fundingAmount -> null - else -> fundingRates.validateRequest(staticParams.nodeParams.nodePrivateKey, fundingScript, open.fundingFeerate, requestFunding) + else -> fundingRates.validateRequest(staticParams.nodeParams.nodePrivateKey, fundingScript, open.fundingFeerate, requestFunding, isChannelCreation = true) } val accept = AcceptDualFundedChannel( temporaryChannelId = open.temporaryChannelId, diff --git a/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt b/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt index d6cb65874..a78ef1756 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt @@ -65,7 +65,7 @@ data class AddWalletInputsToChannel(val walletInputs: List) : data class AddLiquidityForIncomingPayment(val paymentAmount: MilliSatoshi, val requestedAmount: Satoshi, val fundingRate: LiquidityAds.FundingRate, val preimage: ByteVector32, val willAddHtlcs: List) : PeerCommand() { val paymentHash: ByteVector32 = Crypto.sha256(preimage.toByteArray()).byteVector32() - fun fees(fundingFeerate: FeeratePerKw): LiquidityAds.Fees = fundingRate.fees(fundingFeerate, requestedAmount, requestedAmount) + fun fees(fundingFeerate: FeeratePerKw, isChannelCreation: Boolean): LiquidityAds.Fees = fundingRate.fees(fundingFeerate, requestedAmount, requestedAmount, isChannelCreation) } data class PeerConnection(val id: Long, val output: Channel, val logger: MDCLogger) { @@ -600,7 +600,7 @@ class Peer( // The mining fee below pays for the entirety of the splice transaction, including inputs and outputs from the liquidity provider. val (actualFeerate, miningFee) = client.computeSpliceCpfpFeerate(channel.commitments, targetFeerate, spliceWeight = weight, logger) // The mining fee below only covers the remote node's inputs and outputs, which are already included in the mining fee above. - val fundingFees = fundingRate.fees(actualFeerate, amount, amount) + val fundingFees = fundingRate.fees(actualFeerate, amount, amount, isChannelCreation = false) Pair(actualFeerate, ChannelManagementFees(miningFee, fundingFees.serviceFee)) } } @@ -1291,7 +1291,7 @@ class Peer( val dummyFundingScript = Script.write(Scripts.multiSig2of2(Transactions.PlaceHolderPubKey, Transactions.PlaceHolderPubKey)).byteVector() val localMiningFee = Transactions.weight2fee(currentFeerates.fundingFeerate, FundingContributions.computeWeightPaid(isInitiator = true, null, dummyFundingScript, cmd.walletInputs, emptyList())) val localFundingAmount = cmd.totalAmount - localMiningFee - val fundingFees = requestRemoteFunding.fees(currentFeerates.fundingFeerate) + val fundingFees = requestRemoteFunding.fees(currentFeerates.fundingFeerate, isChannelCreation = true) // We also refund the liquidity provider for some of the on-chain fees they will pay for their inputs/outputs of the transaction. // This will be taken from our channel balance during the interactive-tx construction, they shouldn't be deducted from our funding amount. val totalFees = ChannelManagementFees(miningFee = localMiningFee + fundingFees.miningFee, serviceFee = fundingFees.serviceFee) @@ -1347,7 +1347,7 @@ class Peer( val spliceWeight = FundingContributions.computeWeightPaid(isInitiator = true, commitment = available.channel.commitments.active.first(), walletInputs = listOf(), localOutputs = listOf()) val (fundingFeerate, localMiningFee) = client.computeSpliceCpfpFeerate(available.channel.commitments, currentFeerates.fundingFeerate, spliceWeight, logger) val (targetFeerate, paymentDetails) = when { - localBalance >= localMiningFee + cmd.fees(fundingFeerate).total -> { + localBalance >= localMiningFee + cmd.fees(fundingFeerate, isChannelCreation = false).total -> { // We have enough funds to pay the mining fee and the lease fees. // This the ideal scenario because the fees can be paid immediately with the splice transaction. Pair(fundingFeerate, LiquidityAds.PaymentDetails.FromChannelBalanceForFutureHtlc(listOf(cmd.paymentHash))) @@ -1380,7 +1380,7 @@ class Peer( logger.warning { "cannot request on-the-fly splice: payment types not supported (${walletParams.remoteFundingRates.paymentTypes.joinToString()})" } } else -> { - val leaseFees = cmd.fees(targetFeerate) + val leaseFees = cmd.fees(targetFeerate, isChannelCreation = false) val totalFees = ChannelManagementFees(miningFee = localMiningFee.min(localBalance.truncateToSatoshi()) + leaseFees.miningFee, serviceFee = leaseFees.serviceFee) logger.info { "requesting on-the-fly splice for paymentHash=${cmd.paymentHash} feerate=$targetFeerate fee=${totalFees.total} paymentType=${paymentDetails.paymentType}" } val spliceCommand = ChannelCommand.Commitment.Splice.Request( @@ -1406,7 +1406,7 @@ class Peer( // We only need to cover the shared output, which doesn't add too much weight, so we add 25%. val fundingFeerate = currentFeerates.fundingFeerate * 1.25 // We don't pay any local on-chain fees, our fee is only for the liquidity lease. - val leaseFees = cmd.fees(fundingFeerate) + val leaseFees = cmd.fees(fundingFeerate, isChannelCreation = true) val totalFees = ChannelManagementFees(miningFee = leaseFees.miningFee, serviceFee = leaseFees.serviceFee) // We cannot pay the liquidity fees from our channel balance, so we fall back to future HTLCs. val paymentDetails = when { diff --git a/src/commonMain/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandler.kt b/src/commonMain/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandler.kt index aa4eb4885..0cca47533 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandler.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandler.kt @@ -305,7 +305,9 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb, pri when (val fundingRate = remoteFundingRates.findRate(requestedAmount)) { null -> Either.Left(LiquidityEvents.Rejected(requestedAmount.toMilliSatoshi(), 0.msat, LiquidityEvents.Source.OffChainPayment, LiquidityEvents.Rejected.Reason.NoMatchingFundingRate)) else -> { - val fees = fundingRate.fees(currentFeerate, requestedAmount, requestedAmount).total + // We don't know at that point if we'll need a channel or if we already have one. + // We must use the worst case fees that applies to channel creation. + val fees = fundingRate.fees(currentFeerate, requestedAmount, requestedAmount, isChannelCreation = true).total val rejected = when { // We only initiate on-the-fly funding if the missing amount is greater than the fees paid. // Otherwise our peer may not be able to claim the funding fees from the relayed HTLCs. diff --git a/src/commonMain/kotlin/fr/acinq/lightning/wire/LiquidityAds.kt b/src/commonMain/kotlin/fr/acinq/lightning/wire/LiquidityAds.kt index d80c6de6c..135118a6a 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/wire/LiquidityAds.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/wire/LiquidityAds.kt @@ -43,14 +43,16 @@ object LiquidityAds { * The buyer refunds those on-chain fees for the given vbytes. * @param feeProportional proportional fee (expressed in basis points) based on the amount contributed by the seller. * @param feeBase flat fee that must be paid regardless of the amount contributed by the seller. + * @param channelCreationFee flat fee that must be paid when a new channel is created. */ - data class FundingRate(val minAmount: Satoshi, val maxAmount: Satoshi, val fundingWeight: Int, val feeProportional: Int, val feeBase: Satoshi) { + data class FundingRate(val minAmount: Satoshi, val maxAmount: Satoshi, val fundingWeight: Int, val feeProportional: Int, val feeBase: Satoshi, val channelCreationFee: Satoshi) { /** Fees paid by the liquidity buyer. */ - fun fees(feerate: FeeratePerKw, requestedAmount: Satoshi, contributedAmount: Satoshi): Fees { + fun fees(feerate: FeeratePerKw, requestedAmount: Satoshi, contributedAmount: Satoshi, isChannelCreation: Boolean): Fees { val onChainFees = Transactions.weight2fee(feerate, fundingWeight) // If the seller adds more liquidity than requested, the buyer doesn't pay for that extra liquidity. val proportionalFee = requestedAmount.min(contributedAmount) * feeProportional / 10_000 - return Fees(onChainFees, feeBase + proportionalFee) + val flatFee = if (isChannelCreation) channelCreationFee + feeBase else feeBase + return Fees(onChainFees, flatFee + proportionalFee) } /** When liquidity is purchased, the seller provides a signature of the funding rate and funding script. */ @@ -68,6 +70,7 @@ object LiquidityAds { LightningCodecs.writeU16(fundingWeight, out) LightningCodecs.writeU16(feeProportional, out) LightningCodecs.writeU32(feeBase.sat.toInt(), out) + LightningCodecs.writeU32(channelCreationFee.sat.toInt(), out) } companion object { @@ -77,6 +80,7 @@ object LiquidityAds { fundingWeight = LightningCodecs.u16(input), feeProportional = LightningCodecs.u16(input), feeBase = LightningCodecs.u32(input).sat, + channelCreationFee = LightningCodecs.u32(input).sat, ) } } @@ -186,14 +190,14 @@ object LiquidityAds { /** Sellers offer various rates and payment options. */ data class WillFundRates(val fundingRates: List, val paymentTypes: Set) { - fun validateRequest(nodeKey: PrivateKey, fundingScript: ByteVector, fundingFeerate: FeeratePerKw, request: RequestFunding): WillFundPurchase? { + fun validateRequest(nodeKey: PrivateKey, fundingScript: ByteVector, fundingFeerate: FeeratePerKw, request: RequestFunding, isChannelCreation: Boolean): WillFundPurchase? { val paymentTypeOk = paymentTypes.contains(request.paymentDetails.paymentType) val rateOk = fundingRates.contains(request.fundingRate) val amountOk = request.fundingRate.minAmount <= request.requestedAmount && request.requestedAmount <= request.fundingRate.maxAmount return when { paymentTypeOk && rateOk && amountOk -> { val sig = Crypto.sign(request.fundingRate.signedData(fundingScript), nodeKey) - val purchase = Purchase.Standard(request.requestedAmount, request.fees(fundingFeerate), request.paymentDetails) + val purchase = Purchase.Standard(request.requestedAmount, request.fees(fundingFeerate, isChannelCreation), request.paymentDetails) WillFundPurchase(WillFund(request.fundingRate, fundingScript, sig), purchase) } else -> null @@ -242,7 +246,7 @@ object LiquidityAds { /** Request inbound liquidity from a remote peer that supports liquidity ads. */ data class RequestFunding(val requestedAmount: Satoshi, val fundingRate: FundingRate, val paymentDetails: PaymentDetails) { - fun fees(feerate: FeeratePerKw): Fees = fundingRate.fees(feerate, requestedAmount, requestedAmount) + fun fees(feerate: FeeratePerKw, isChannelCreation: Boolean): Fees = fundingRate.fees(feerate, requestedAmount, requestedAmount, isChannelCreation) fun validateRemoteFunding( remoteNodeId: PublicKey, @@ -250,6 +254,7 @@ object LiquidityAds { fundingScript: ByteVector, remoteFundingAmount: Satoshi, fundingFeerate: FeeratePerKw, + isChannelCreation: Boolean, willFund: WillFund? ): Either { return when (willFund) { @@ -261,7 +266,7 @@ object LiquidityAds { remoteFundingAmount < requestedAmount -> Either.Left(InvalidLiquidityAdsAmount(channelId, remoteFundingAmount, requestedAmount)) else -> { val purchasedAmount = requestedAmount.min(remoteFundingAmount) - val fees = fundingRate.fees(fundingFeerate, requestedAmount, remoteFundingAmount) + val fees = fundingRate.fees(fundingFeerate, requestedAmount, remoteFundingAmount, isChannelCreation) Either.Right(Purchase.Standard(purchasedAmount, fees, paymentDetails)) } } @@ -295,11 +300,12 @@ object LiquidityAds { fundingScript: ByteVector, remoteFundingAmount: Satoshi, fundingFeerate: FeeratePerKw, + isChannelCreation: Boolean, willFund: WillFund?, ): Either { return when (request) { null -> Either.Right(null) - else -> request.validateRemoteFunding(remoteNodeId, channelId, fundingScript, remoteFundingAmount, fundingFeerate, willFund) + else -> request.validateRemoteFunding(remoteNodeId, channelId, fundingScript, remoteFundingAmount, fundingFeerate, isChannelCreation, willFund) } } diff --git a/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt index 9152669aa..b8f735bdd 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt @@ -190,7 +190,7 @@ class SpliceTestsCommon : LightningTestSuite() { fun `splice to purchase inbound liquidity`() { val (alice, bob) = reachNormal() val fundingRates = LiquidityAds.WillFundRates( - fundingRates = listOf(LiquidityAds.FundingRate(100_000.sat, 500_000.sat, 0, 250 /* 2.5% */, 0.sat)), + fundingRates = listOf(LiquidityAds.FundingRate(100_000.sat, 500_000.sat, 0, 250 /* 2.5% */, 0.sat, 1000.sat)), paymentTypes = setOf(LiquidityAds.PaymentType.FromChannelBalance), ) val liquidityRequest = LiquidityAds.RequestFunding(200_000.sat, fundingRates.findRate(200_000.sat)!!, LiquidityAds.PaymentDetails.FromChannelBalance) @@ -205,7 +205,7 @@ class SpliceTestsCommon : LightningTestSuite() { assertNull(defaultSpliceAck.willFund) val fundingScript = Helpers.Funding.makeFundingPubKeyScript(spliceInit.fundingPubkey, defaultSpliceAck.fundingPubkey) run { - val willFund = fundingRates.validateRequest(bob.staticParams.nodeParams.nodePrivateKey, fundingScript, cmd.feerate, spliceInit.requestFunding!!)?.willFund + val willFund = fundingRates.validateRequest(bob.staticParams.nodeParams.nodePrivateKey, fundingScript, cmd.feerate, spliceInit.requestFunding!!, isChannelCreation = false)?.willFund assertNotNull(willFund) val spliceAck = SpliceAck(alice.channelId, liquidityRequest.requestedAmount, 0.msat, defaultSpliceAck.fundingPubkey, willFund) val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(spliceAck)) @@ -215,7 +215,7 @@ class SpliceTestsCommon : LightningTestSuite() { } run { // Bob uses a different funding script than what Alice expects. - val willFund = fundingRates.validateRequest(bob.staticParams.nodeParams.nodePrivateKey, ByteVector("deadbeef"), cmd.feerate, spliceInit.requestFunding!!)?.willFund + val willFund = fundingRates.validateRequest(bob.staticParams.nodeParams.nodePrivateKey, ByteVector("deadbeef"), cmd.feerate, spliceInit.requestFunding!!, isChannelCreation = false)?.willFund assertNotNull(willFund) val spliceAck = SpliceAck(alice.channelId, liquidityRequest.requestedAmount, 0.msat, defaultSpliceAck.fundingPubkey, willFund) val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(spliceAck)) @@ -237,10 +237,10 @@ class SpliceTestsCommon : LightningTestSuite() { @OptIn(ExperimentalCoroutinesApi::class) fun `splice to purchase inbound liquidity -- not enough funds`() { val (alice, bob) = reachNormal(aliceFundingAmount = 100_000.sat, bobFundingAmount = 10_000.sat, alicePushAmount = 0.msat, bobPushAmount = 0.msat) - val fundingRate = LiquidityAds.FundingRate(100_000.sat, 10_000_000.sat, 0, 100 /* 1% */, 1.sat) + val fundingRate = LiquidityAds.FundingRate(100_000.sat, 10_000_000.sat, 0, 100 /* 1% */, 1.sat, 1000.sat) run { val liquidityRequest = LiquidityAds.RequestFunding(1_000_000.sat, fundingRate, LiquidityAds.PaymentDetails.FromChannelBalance) - assertEquals(10_001.sat, liquidityRequest.fees(FeeratePerKw(1000.sat)).total) + assertEquals(10_001.sat, liquidityRequest.fees(FeeratePerKw(1000.sat), isChannelCreation = false).total) val cmd = ChannelCommand.Commitment.Splice.Request(CompletableDeferred(), null, null, liquidityRequest, FeeratePerKw(1000.sat), listOf()) val (bob1, actionsBob1) = bob.process(cmd) val bobStfu = actionsBob1.findOutgoingMessage() @@ -255,7 +255,7 @@ class SpliceTestsCommon : LightningTestSuite() { } run { val liquidityRequest = LiquidityAds.RequestFunding(1_000_000.sat, fundingRate.copy(feeBase = 0.sat), LiquidityAds.PaymentDetails.FromChannelBalance) - assertEquals(10_000.sat, liquidityRequest.fees(FeeratePerKw(1000.sat)).total) + assertEquals(10_000.sat, liquidityRequest.fees(FeeratePerKw(1000.sat), isChannelCreation = false).total) val cmd = ChannelCommand.Commitment.Splice.Request(CompletableDeferred(), null, null, liquidityRequest, FeeratePerKw(1000.sat), listOf()) val (bob1, actionsBob1) = bob.process(cmd) val bobStfu = actionsBob1.findOutgoingMessage() @@ -267,7 +267,7 @@ class SpliceTestsCommon : LightningTestSuite() { run { // When we don't have enough funds in our channel balance, fees can be paid via future HTLCs. val liquidityRequest = LiquidityAds.RequestFunding(1_000_000.sat, fundingRate, LiquidityAds.PaymentDetails.FromFutureHtlc(listOf(randomBytes32()))) - assertEquals(10_001.sat, liquidityRequest.fees(FeeratePerKw(1000.sat)).total) + assertEquals(10_001.sat, liquidityRequest.fees(FeeratePerKw(1000.sat), isChannelCreation = false).total) val cmd = ChannelCommand.Commitment.Splice.Request(CompletableDeferred(), null, null, liquidityRequest, FeeratePerKw(1000.sat), listOf()) val (bob1, actionsBob1) = bob.process(cmd) val bobStfu = actionsBob1.findOutgoingMessage() @@ -282,7 +282,7 @@ class SpliceTestsCommon : LightningTestSuite() { @OptIn(ExperimentalCoroutinesApi::class) fun `splice to purchase inbound liquidity -- not enough funds but on-the-fly funding`() { val (alice, bob) = reachNormal(channelType = ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, bobFundingAmount = 0.sat, alicePushAmount = 0.msat, bobPushAmount = 0.msat) - val fundingRate = LiquidityAds.FundingRate(0.sat, 500_000.sat, 0, 50, 0.sat) + val fundingRate = LiquidityAds.FundingRate(0.sat, 500_000.sat, 0, 50, 0.sat, 1000.sat) val origin = Origin.OffChainPayment(randomBytes32(), 25_000_000.msat, ChannelManagementFees(0.sat, 500.sat)) run { // We don't have enough funds to pay fees from our channel balance. diff --git a/src/commonTest/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandlerTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandlerTestsCommon.kt index ba3d0ff6f..0f004b531 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandlerTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandlerTestsCommon.kt @@ -399,11 +399,11 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { fun `receive will_add_htlc with a fee too high`() = runSuspendTest { val fundingRates = LiquidityAds.WillFundRates( // Note that we use a fixed liquidity fees to make testing easier. - fundingRates = listOf(LiquidityAds.FundingRate(0.sat, 250_000.sat, 0, 0, 5_000.sat)), + fundingRates = listOf(LiquidityAds.FundingRate(0.sat, 250_000.sat, 0, 0, 5_000.sat, 0.sat)), paymentTypes = setOf(LiquidityAds.PaymentType.FromChannelBalance, LiquidityAds.PaymentType.FromChannelBalanceForFutureHtlc, LiquidityAds.PaymentType.FromFutureHtlc), ) val inboundLiquidityTarget = 100_000.sat - assertEquals(5_000.sat, fundingRates.fundingRates.first().fees(TestConstants.feeratePerKw, inboundLiquidityTarget, inboundLiquidityTarget).total) + assertEquals(5_000.sat, fundingRates.fundingRates.first().fees(TestConstants.feeratePerKw, inboundLiquidityTarget, inboundLiquidityTarget, isChannelCreation = false).total) val defaultPolicy = LiquidityPolicy.Auto(inboundLiquidityTarget, maxAbsoluteFee = 5_000.sat, maxRelativeFeeBasisPoints = 500, skipAbsoluteFeeCheck = false) val testCases = listOf( // If payment amount is at least twice the fees, we accept the payment. @@ -627,7 +627,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { // The splice transaction is successfully signed and stored in the DB. val purchase = LiquidityAds.Purchase.Standard( splice.requestedAmount, - splice.fees(TestConstants.feeratePerKw), + splice.fees(TestConstants.feeratePerKw, isChannelCreation = false), LiquidityAds.PaymentDetails.FromFutureHtlc(listOf(incomingPayment.paymentHash)), ) val payment = InboundLiquidityOutgoingPayment(UUID.randomUUID(), channelId, TxId(randomBytes32()), 500.sat, purchase, 0, null, null) diff --git a/src/commonTest/kotlin/fr/acinq/lightning/tests/TestConstants.kt b/src/commonTest/kotlin/fr/acinq/lightning/tests/TestConstants.kt index 3e3b3762a..6d3583f56 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/tests/TestConstants.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/tests/TestConstants.kt @@ -40,8 +40,8 @@ object TestConstants { val fundingRates = LiquidityAds.WillFundRates( fundingRates = listOf( - LiquidityAds.FundingRate(100_000.sat, 500_000.sat, 500, 100, 0.sat), - LiquidityAds.FundingRate(500_000.sat, 10_000_000.sat, 750, 100, 0.sat) + LiquidityAds.FundingRate(100_000.sat, 500_000.sat, 500, 100, 0.sat, 0.sat), + LiquidityAds.FundingRate(500_000.sat, 10_000_000.sat, 750, 100, 0.sat, 0.sat) ), paymentTypes = setOf( LiquidityAds.PaymentType.FromChannelBalance, diff --git a/src/commonTest/kotlin/fr/acinq/lightning/wire/LightningCodecsTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/wire/LightningCodecsTestsCommon.kt index 07acb9573..c22e49694 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/wire/LightningCodecsTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/wire/LightningCodecsTestsCommon.kt @@ -227,25 +227,25 @@ class LightningCodecsTestsCommon : LightningTestSuite() { ), // network and unknown odd records TestCase(ByteVector("0000 0002088a 01200101010101010101010101010101010101010101010101010101010101010101 02012a"), decoded = null), // network and unknown even records TestCase( - ByteVector("0000 0002088a fd053b150001000186a00007a1200226006400001388000101"), + ByteVector("0000 0002088a fd053b190001000186a00007a1200226006400001388000003e8000101"), Init( Features(ByteVector("088a")), chainHashs = listOf(), liquidityRates = LiquidityAds.WillFundRates( - fundingRates = listOf(LiquidityAds.FundingRate(100_000.sat, 500_000.sat, 550, 100, 5_000.sat)), + fundingRates = listOf(LiquidityAds.FundingRate(100_000.sat, 500_000.sat, 550, 100, 5_000.sat, 1_000.sat)), paymentTypes = setOf(LiquidityAds.PaymentType.FromChannelBalance) ) ), ), // one liquidity ads with the default payment type TestCase( - ByteVector("0000 0002088a fd053b3f0002000186a00007a12002260064000013880007a120004c4b40044c004b00000000001b080000000000000000000700000000000000000000000000000001"), + ByteVector("0000 0002088a fd053b470002000186a00007a1200226006400001388000003e80007a120004c4b40044c004b00000000000005dc001b080000000000000000000700000000000000000000000000000001"), Init( Features(ByteVector("088a")), chainHashs = listOf(), liquidityRates = LiquidityAds.WillFundRates( fundingRates = listOf( - LiquidityAds.FundingRate(100_000.sat, 500_000.sat, 550, 100, 5_000.sat), - LiquidityAds.FundingRate(500_000.sat, 5_000_000.sat, 1100, 75, 0.sat), + LiquidityAds.FundingRate(100_000.sat, 500_000.sat, 550, 100, 5_000.sat, 1_000.sat), + LiquidityAds.FundingRate(500_000.sat, 5_000_000.sat, 1100, 75, 0.sat, 1_500.sat), ), paymentTypes = setOf( LiquidityAds.PaymentType.FromChannelBalance, @@ -313,8 +313,8 @@ class LightningCodecsTestsCommon : LightningTestSuite() { fun `encode - decode open_channel`() { val fundingRates = LiquidityAds.WillFundRates( fundingRates = listOf( - LiquidityAds.FundingRate(100_000.sat, 500_000.sat, 550, 100, 5_000.sat), - LiquidityAds.FundingRate(500_000.sat, 5_000_000.sat, 1100, 75, 0.sat), + LiquidityAds.FundingRate(100_000.sat, 500_000.sat, 550, 100, 5_000.sat, 1_000.sat), + LiquidityAds.FundingRate(500_000.sat, 5_000_000.sat, 1100, 75, 0.sat, 1_500.sat), ), paymentTypes = setOf( LiquidityAds.PaymentType.FromChannelBalance, @@ -339,9 +339,9 @@ class LightningCodecsTestsCommon : LightningTestSuite() { defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs))) to (defaultEncoded + ByteVector("0103101000")), defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs), ChannelTlv.PushAmountTlv(25_000.msat))) to (defaultEncoded + ByteVector("0103101000 fe470000070261a8")), defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs), ChannelTlv.RequireConfirmedInputsTlv)) to (defaultEncoded + ByteVector("0103101000 0200")), - defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs), ChannelTlv.RequestFundingTlv(requestFundsFromChannelBalance))) to (defaultEncoded + ByteVector("0103101000 fd053b1a00000000000b71b00007a120004c4b40044c004b000000000000")), - defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs), ChannelTlv.RequestFundingTlv(requestFundsFromHtlc))) to (defaultEncoded + ByteVector("0103101000 fd053b5a000000000007a120000186a00007a1200226006400001388804080417c0c91deb72606958425ea1552a045a55a250e91870231b486dcb2106734d662b36d54c6d1c2a0227cdc114d12c578c25ab6ec664eebaa440d7e493eba47")), - defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs), ChannelTlv.RequestFundingTlv(requestFundsFromBalanceForHtlc))) to (defaultEncoded + ByteVector("0103101000 fd053b5a000000000007a120000186a00007a1200226006400001388824080417c0c91deb72606958425ea1552a045a55a250e91870231b486dcb2106734d662b36d54c6d1c2a0227cdc114d12c578c25ab6ec664eebaa440d7e493eba47")), + defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs), ChannelTlv.RequestFundingTlv(requestFundsFromChannelBalance))) to (defaultEncoded + ByteVector("0103101000 fd053b1e00000000000b71b00007a120004c4b40044c004b00000000000005dc0000")), + defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs), ChannelTlv.RequestFundingTlv(requestFundsFromHtlc))) to (defaultEncoded + ByteVector("0103101000 fd053b5e000000000007a120000186a00007a1200226006400001388000003e8804080417c0c91deb72606958425ea1552a045a55a250e91870231b486dcb2106734d662b36d54c6d1c2a0227cdc114d12c578c25ab6ec664eebaa440d7e493eba47")), + defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs), ChannelTlv.RequestFundingTlv(requestFundsFromBalanceForHtlc))) to (defaultEncoded + ByteVector("0103101000 fd053b5e000000000007a120000186a00007a1200226006400001388000003e8824080417c0c91deb72606958425ea1552a045a55a250e91870231b486dcb2106734d662b36d54c6d1c2a0227cdc114d12c578c25ab6ec664eebaa440d7e493eba47")), defaultOpen.copy(tlvStream = TlvStream(setOf(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs)), setOf(GenericTlv(321, ByteVector("2a2a")), GenericTlv(325, ByteVector("02"))))) to (defaultEncoded + ByteVector("0103101000 fd0141022a2a fd01450102")), ) // @formatter:on @@ -378,10 +378,10 @@ class LightningCodecsTestsCommon : LightningTestSuite() { @Test fun `encode - decode accept_channel`() { val nodeKey = PrivateKey.fromHex("57ac961f1b80ebfb610037bf9c96c6333699bde42257919a53974811c34649e3") - val fundingLease = LiquidityAds.FundingRate(500_000.sat, 5_000_000.sat, 1100, 75, 0.sat) + val fundingLease = LiquidityAds.FundingRate(500_000.sat, 5_000_000.sat, 1100, 75, 0.sat, 1_500.sat) val requestFunds = LiquidityAds.RequestFunding(750_000.sat, fundingLease, LiquidityAds.PaymentDetails.FromChannelBalance) val fundingScript = Helpers.Funding.makeFundingPubKeyScript(publicKey(1), publicKey(1)) - val willFund = LiquidityAds.WillFundRates(listOf(fundingLease), setOf(LiquidityAds.PaymentType.FromChannelBalance)).validateRequest(nodeKey, fundingScript, FeeratePerKw(5000.sat), requestFunds)!!.willFund + val willFund = LiquidityAds.WillFundRates(listOf(fundingLease), setOf(LiquidityAds.PaymentType.FromChannelBalance)).validateRequest(nodeKey, fundingScript, FeeratePerKw(5000.sat), requestFunds, isChannelCreation = true)!!.willFund // @formatter:off val defaultAccept = AcceptDualFundedChannel(ByteVector32.One, 50_000.sat, 473.sat, 100_000_000, 1.msat, 6, CltvExpiryDelta(144), 50, publicKey(1), point(2), point(3), point(4), point(5), point(6), publicKey(7)) val defaultEncoded = ByteVector("0041 0100000000000000000000000000000000000000000000000000000000000000 000000000000c350 00000000000001d9 0000000005f5e100 0000000000000001 00000006 0090 0032 031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f 024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766 02531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe337 03462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b 0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f7 03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a 02989c0b76cb563971fdc9bef31ec06c3560f3249d6ee9e5d83c57625596e05f6f") @@ -389,7 +389,7 @@ class LightningCodecsTestsCommon : LightningTestSuite() { defaultAccept to defaultEncoded, defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.UnsupportedChannelType(Features(Feature.StaticRemoteKey to FeatureSupport.Mandatory))))) to (defaultEncoded + ByteVector("01021000")), defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector("01abcdef")), ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs))) to (defaultEncoded + ByteVector("000401abcdef 0103101000")), - defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs), ChannelTlv.ProvideFundingTlv(willFund))) to (defaultEncoded + ByteVector("0103101000 fd053b740007a120004c4b40044c004b00000000002200202ec38203f4cf37a3b377d9a55c7ae0153c643046dbdbe2ffccfb11b74420103c35962783e077e3c5214ba829752be2a3994a7c5e0e9d735ef5a9dab3ce1d6dda6282c3252b20af52e58c33c0e164167fd59e19114a8a8f9eb76b33008205dcb6")), + defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs), ChannelTlv.ProvideFundingTlv(willFund))) to (defaultEncoded + ByteVector("0103101000 fd053b780007a120004c4b40044c004b00000000000005dc002200202ec38203f4cf37a3b377d9a55c7ae0153c643046dbdbe2ffccfb11b74420103cc57cf393f6bd534472ec08cbfbbc7268501b32f563a21cdf02a99127c4f25168249acd6509f96b2e93843c3b838ee4808c75d0a15ff71ba886fda980b8ca954f")), defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs), ChannelTlv.PushAmountTlv(1729.msat))) to (defaultEncoded + ByteVector("0103101000 fe470000070206c1")), defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs), ChannelTlv.RequireConfirmedInputsTlv)) to (defaultEncoded + ByteVector("0103101000 0200")), defaultAccept.copy(tlvStream = TlvStream(setOf(ChannelTlv.ChannelTypeTlv(ChannelType.SupportedChannelType.AnchorOutputs)), setOf(GenericTlv(113, ByteVector("deadbeef"))))) to (defaultEncoded + ByteVector("0103101000 7104deadbeef")), @@ -528,7 +528,7 @@ class LightningCodecsTestsCommon : LightningTestSuite() { val channelId = ByteVector32("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") val fundingTxId = TxId(TxHash("24e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f14566")) val fundingPubkey = PublicKey(ByteVector.fromHex("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")) - val fundingRate = LiquidityAds.FundingRate(100_000.sat, 100_000.sat, 400, 150, 0.sat) + val fundingRate = LiquidityAds.FundingRate(100_000.sat, 100_000.sat, 400, 150, 0.sat, 0.sat) val testCases = listOf( // @formatter:off Stfu(channelId, false) to ByteVector("0002 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00"), @@ -537,12 +537,12 @@ class LightningCodecsTestsCommon : LightningTestSuite() { SpliceInit(channelId, 150_000.sat, 25_000_000.msat, FeeratePerKw(2500.sat), 100, fundingPubkey, null) to ByteVector("9088 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000249f0 000009c4 00000064 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fe4700000704017d7840"), SpliceInit(channelId, 0.sat, FeeratePerKw(500.sat), 0, fundingPubkey) to ByteVector("9088 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000000000 000001f4 00000000 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), SpliceInit(channelId, (-50_000).sat, FeeratePerKw(500.sat), 0, fundingPubkey) to ByteVector("9088 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ffffffffffff3cb0 000001f4 00000000 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), - SpliceInit(channelId, 100_000.sat, 0.msat, FeeratePerKw(2500.sat), 100, fundingPubkey, LiquidityAds.RequestFunding(100_000.sat, fundingRate, LiquidityAds.PaymentDetails.FromChannelBalance)) to ByteVector("9088 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000186a0 000009c4 00000064 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fd053b1a00000000000186a0000186a0000186a001900096000000000000"), + SpliceInit(channelId, 100_000.sat, 0.msat, FeeratePerKw(2500.sat), 100, fundingPubkey, LiquidityAds.RequestFunding(100_000.sat, fundingRate, LiquidityAds.PaymentDetails.FromChannelBalance)) to ByteVector("9088 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000186a0 000009c4 00000064 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fd053b1e00000000000186a0000186a0000186a00190009600000000000000000000"), SpliceAck(channelId, 25_000.sat, fundingPubkey) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), SpliceAck(channelId, 40_000.sat, 10_000_000.msat, fundingPubkey, null) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000009c40 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fe4700000703989680"), SpliceAck(channelId, 0.sat, fundingPubkey) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0000000000000000 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), SpliceAck(channelId, (-25_000).sat, fundingPubkey) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ffffffffffff9e58 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), - SpliceAck(channelId, 25_000.sat, 0.msat, fundingPubkey, LiquidityAds.WillFund(fundingRate, ByteVector("deadbeef"), ByteVector64.Zeroes)) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fd053b56000186a0000186a001900096000000000004deadbeef00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + SpliceAck(channelId, 25_000.sat, 0.msat, fundingPubkey, LiquidityAds.WillFund(fundingRate, ByteVector("deadbeef"), ByteVector64.Zeroes)) to ByteVector("908a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 00000000000061a8 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 fd053b5a000186a0000186a00190009600000000000000000004deadbeef00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), SpliceLocked(channelId, fundingTxId) to ByteVector("908c aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 24e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f14566"), // @formatter:on ) @@ -886,11 +886,11 @@ class LightningCodecsTestsCommon : LightningTestSuite() { @Test fun `decode unknown liquidity ads types`() { - val fundingRate = LiquidityAds.FundingRate(100_000.sat, 500_000.sat, 550, 100, 5_000.sat) + val fundingRate = LiquidityAds.FundingRate(100_000.sat, 500_000.sat, 550, 100, 5_000.sat, 0.sat) val testCases = mapOf( // @formatter:off - ByteVector("0001 000186a00007a1200226006400001388 0001 01") to LiquidityAds.WillFundRates(listOf(fundingRate), setOf(LiquidityAds.PaymentType.FromChannelBalance)), - ByteVector("0001 000186a00007a1200226006400001388 001b 080000000000000000000000000000000008000000000000000001") to LiquidityAds.WillFundRates(listOf(fundingRate), setOf(LiquidityAds.PaymentType.FromChannelBalance, LiquidityAds.PaymentType.Unknown(75), LiquidityAds.PaymentType.Unknown(211))), + ByteVector("0001 000186a00007a120022600640000138800000000 0001 01") to LiquidityAds.WillFundRates(listOf(fundingRate), setOf(LiquidityAds.PaymentType.FromChannelBalance)), + ByteVector("0001 000186a00007a120022600640000138800000000 001b 080000000000000000000000000000000008000000000000000001") to LiquidityAds.WillFundRates(listOf(fundingRate), setOf(LiquidityAds.PaymentType.FromChannelBalance, LiquidityAds.PaymentType.Unknown(75), LiquidityAds.PaymentType.Unknown(211))), // @formatter:on ) testCases.forEach { diff --git a/src/commonTest/kotlin/fr/acinq/lightning/wire/LiquidityAdsTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/wire/LiquidityAdsTestsCommon.kt index b94138c7b..69c619612 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/wire/LiquidityAdsTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/wire/LiquidityAdsTestsCommon.kt @@ -22,21 +22,22 @@ class LiquidityAdsTestsCommon : LightningTestSuite() { val nodeKey = PrivateKey.fromHex("57ac961f1b80ebfb610037bf9c96c6333699bde42257919a53974811c34649e3") assertEquals(PublicKey.fromHex("03ca9b880627d2d4e3b33164f66946349f820d26aa9572fe0e525e534850cbd413"), nodeKey.publicKey()) - val fundingRate = LiquidityAds.FundingRate(100_000.sat, 1_000_000.sat, 500, 100, 10.sat) - assertEquals(fundingRate.fees(FeeratePerKw(FeeratePerByte(5.sat)), 500_000.sat, 500_000.sat).total, 5635.sat) - assertEquals(fundingRate.fees(FeeratePerKw(FeeratePerByte(5.sat)), 500_000.sat, 600_000.sat).total, 5635.sat) - assertEquals(fundingRate.fees(FeeratePerKw(FeeratePerByte(5.sat)), 500_000.sat, 400_000.sat).total, 4635.sat) - assertEquals(fundingRate.fees(FeeratePerKw(FeeratePerByte(10.sat)), 500_000.sat, 500_000.sat).total, 6260.sat) + val fundingRate = LiquidityAds.FundingRate(100_000.sat, 1_000_000.sat, 500, 100, 10.sat, 1000.sat) + assertEquals(fundingRate.fees(FeeratePerKw(FeeratePerByte(5.sat)), 500_000.sat, 500_000.sat, isChannelCreation = false).total, 5635.sat) + assertEquals(fundingRate.fees(FeeratePerKw(FeeratePerByte(5.sat)), 500_000.sat, 600_000.sat, isChannelCreation = false).total, 5635.sat) + assertEquals(fundingRate.fees(FeeratePerKw(FeeratePerByte(5.sat)), 500_000.sat, 600_000.sat, isChannelCreation = true).total, 6635.sat) + assertEquals(fundingRate.fees(FeeratePerKw(FeeratePerByte(5.sat)), 500_000.sat, 400_000.sat, isChannelCreation = false).total, 4635.sat) + assertEquals(fundingRate.fees(FeeratePerKw(FeeratePerByte(10.sat)), 500_000.sat, 500_000.sat, isChannelCreation = false).total, 6260.sat) val fundingRates = LiquidityAds.WillFundRates(listOf(fundingRate), setOf(LiquidityAds.PaymentType.FromChannelBalance)) val request = LiquidityAds.RequestFunding.chooseRate(500_000.sat, LiquidityAds.PaymentDetails.FromChannelBalance, fundingRates) assertNotNull(request) val fundingScript = ByteVector.fromHex("00202395c9c52c02ca069f1d56a3c6124bf8b152a617328c76e6b31f83ace370c2ff") - val willFund = fundingRates.validateRequest(nodeKey, fundingScript, FeeratePerKw(1000.sat), request)?.willFund + val willFund = fundingRates.validateRequest(nodeKey, fundingScript, FeeratePerKw(1000.sat), request, isChannelCreation = true)?.willFund assertNotNull(willFund) assertEquals(fundingScript, willFund.fundingScript) assertEquals(fundingRate, willFund.fundingRate) - assertEquals(ByteVector64.fromValidHex("0d99b73ecc32a81581cb761d8737e8bccf2358a01f7dea8e2f2579f32db42e94668786a2245287848c550b502fee9aca232c0c343afb16ac44d9be9c59d16f70"), willFund.signature) + assertEquals(ByteVector64.fromValidHex("a53106bd20027b0215480ff0b06b2bf9324bb257c2a0e74c2604ec347493f90d3a975d56a68b21a6cc48d6763d96f70e1d630dd1720cf6b7314d4304050fe265"), willFund.signature) data class TestCase(val remoteFundingAmount: Satoshi, val willFund: LiquidityAds.WillFund?, val failure: ChannelException?) @@ -48,7 +49,7 @@ class LiquidityAdsTestsCommon : LightningTestSuite() { TestCase(0.sat, willFund, failure = InvalidLiquidityAdsAmount(channelId, 0.sat, 500_000.sat)), ) testCases.forEach { - val result = request.validateRemoteFunding(nodeKey.publicKey(), channelId, fundingScript, it.remoteFundingAmount, FeeratePerKw(2500.sat), it.willFund) + val result = request.validateRemoteFunding(nodeKey.publicKey(), channelId, fundingScript, it.remoteFundingAmount, FeeratePerKw(2500.sat), isChannelCreation = true, it.willFund) assertEquals(it.failure, result.left) } }