From 025aa272eb7b4744374636a449df29e0c08df268 Mon Sep 17 00:00:00 2001 From: t-bast Date: Mon, 23 Oct 2023 16:35:41 +0200 Subject: [PATCH] Apply 0-reserve to both peers We previously only applied 0-reserve to the non-initiator when the feature bit was active. We should apply it to both sides of the channel. This reduces the liquidity requirements on the initiator side, and allows receiving more funds over lightning before requiring a splice to get more inbound liquidity. --- .../fr/acinq/lightning/channel/Commitments.kt | 17 +++++------------ .../channel/states/NormalTestsCommon.kt | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/channel/Commitments.kt b/src/commonMain/kotlin/fr/acinq/lightning/channel/Commitments.kt index 7868688d5..41c88f08b 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/channel/Commitments.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/channel/Commitments.kt @@ -146,12 +146,12 @@ data class Commitment( val fundingAmount: Satoshi = commitInput.txOut.amount fun localChannelReserve(params: ChannelParams): Satoshi = when { - params.channelFeatures.hasFeature(Feature.ZeroReserveChannels) && !params.localParams.isInitiator -> 0.sat + params.channelFeatures.hasFeature(Feature.ZeroReserveChannels) -> 0.sat else -> (fundingAmount / 100).max(params.remoteParams.dustLimit) } fun remoteChannelReserve(params: ChannelParams): Satoshi = when { - params.channelFeatures.hasFeature(Feature.ZeroReserveChannels) && params.localParams.isInitiator -> 0.sat + params.channelFeatures.hasFeature(Feature.ZeroReserveChannels) -> 0.sat else -> (fundingAmount / 100).max(params.localParams.dustLimit) } @@ -390,14 +390,7 @@ data class Commitment( // and it would be tricky to check if the conditions are met at signing // (it also means that we need to check the fee of the initial commitment tx somewhere) val fees = commitTxFee(params.localParams.dustLimit, reduced) - // TODO: - // When migrating to the dual-funded model, we removed the explicit channel reserve from LocalParams. - // For channels that were created before the splicing update, this can result in a mismatch where we think - // the channel reserve is bigger than what is actually is, and incorrectly reject the remote update_fee. - // We temporarily ignore the channel reserve to avoid unnecessary force-close. - // We should restore the correct calculation that takes the reserve into account once all users have migrated. - // val missing = reduced.toRemote.truncateToSatoshi() - remoteChannelReserve(params) - fees - val missing = reduced.toRemote.truncateToSatoshi() - fees + val missing = reduced.toRemote.truncateToSatoshi() - remoteChannelReserve(params) - fees return if (missing < 0.sat) { Either.Left(CannotAffordFees(params.channelId, -missing, remoteChannelReserve(params), fees)) } else { @@ -515,11 +508,11 @@ data class FullCommitment( val fundingTxId: ByteVector32 = commitInput.outPoint.txid val fundingAmount = commitInput.txOut.amount val localChannelReserve = when { - params.channelFeatures.hasFeature(Feature.ZeroReserveChannels) && !params.localParams.isInitiator -> 0.sat + params.channelFeatures.hasFeature(Feature.ZeroReserveChannels) -> 0.sat else -> (fundingAmount / 100).max(params.remoteParams.dustLimit) } val remoteChannelReserve = when { - params.channelFeatures.hasFeature(Feature.ZeroReserveChannels) && params.localParams.isInitiator -> 0.sat + params.channelFeatures.hasFeature(Feature.ZeroReserveChannels) -> 0.sat else -> (fundingAmount / 100).max(params.localParams.dustLimit) } } diff --git a/src/commonTest/kotlin/fr/acinq/lightning/channel/states/NormalTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/channel/states/NormalTestsCommon.kt index 0494f27c2..297e9bbd1 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/channel/states/NormalTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/channel/states/NormalTestsCommon.kt @@ -429,6 +429,21 @@ class NormalTestsCommon : LightningTestSuite() { assertEquals(alice1.commitments.changes.remoteChanges.proposed, listOf(add)) } + @Test + fun `recv UpdateAddHtlc -- zero-conf -- zero-reserve -- initiator`() { + val (alice0, bob0) = reachNormal(ChannelType.SupportedChannelType.AnchorOutputsZeroReserve, aliceFundingAmount = 10_000_000.sat, bobFundingAmount = 5_000_000.sat, alicePushAmount = 9_800_000_000.msat, zeroConf = true) + assertEquals(alice0.commitments.latest.fundingAmount, 15_000_000.sat) + assertEquals(alice0.commitments.latest.localCommit.spec.toLocal, 200_000_000.msat) + // If we applied the default 1% reserve, Alice's balance couldn't go below 150 000 sats. + // Alice still needs to pay the commit tx fees, so she cannot spend her entire balance. + val (nodes1, r, htlc) = addHtlc(160_000_000.msat, alice0, bob0) + val (alice2, bob2) = crossSign(nodes1.first, nodes1.second) + val (alice3, bob3) = fulfillHtlc(htlc.id, r, alice2, bob2) + val (bob4, alice4) = crossSign(bob3, alice3) + assertEquals(alice4.commitments.latest.localCommit.spec.toLocal, 40_000_000.msat) + assertEquals(bob4.commitments.latest.localCommit.spec.toRemote, 40_000_000.msat) + } + @Test fun `recv UpdateAddHtlc -- unexpected id`() { val (_, bob0) = reachNormal() @@ -1419,8 +1434,7 @@ class NormalTestsCommon : LightningTestSuite() { assertEquals(bob.commitments.copy(changes = bob.commitments.changes.copy(remoteChanges = bob.commitments.changes.remoteChanges.copy(proposed = bob.commitments.changes.remoteChanges.proposed + fee2))), bob2.commitments) } - // TODO: restore this test once we take the reserve into account (see canReceiveFee) - @Ignore + @Test fun `recv UpdateFee -- sender cannot afford it`() { val (alice, bob) = reachNormal() // We put all the balance on Bob's side, so that Alice cannot afford a feerate increase.