Skip to content

Commit 5be613d

Browse files
committed
Fix anchor outputs closing fee requirements
When using anchor outputs, the mutual close fee is allowed to be greater than the commit tx fee, because we're targeting a specific confirmation window.
1 parent f283393 commit 5be613d

File tree

3 files changed

+10
-10
lines changed

3 files changed

+10
-10
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,12 @@ object Helpers {
427427

428428
def firstClosingFee(commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): Satoshi = {
429429
val requestedFeerate = feeEstimator.getFeeratePerKw(feeTargets.mutualCloseBlockTarget)
430-
// we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction"
431-
val feeratePerKw = requestedFeerate.min(commitments.localCommit.spec.feeratePerKw)
430+
val feeratePerKw = if (commitments.channelVersion.hasAnchorOutputs) {
431+
requestedFeerate
432+
} else {
433+
// we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction"
434+
requestedFeerate.min(commitments.localCommit.spec.feeratePerKw)
435+
}
432436
firstClosingFee(commitments, localScriptPubkey, remoteScriptPubkey, feeratePerKw)
433437
}
434438

@@ -456,7 +460,7 @@ object Helpers {
456460
def checkClosingSignature(keyManager: ChannelKeyManager, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, remoteClosingFee: Satoshi, remoteClosingSig: ByteVector64)(implicit log: LoggingAdapter): Either[ChannelException, Transaction] = {
457461
import commitments._
458462
val lastCommitFeeSatoshi = commitments.commitInput.txOut.amount - commitments.localCommit.publishableTxs.commitTx.tx.txOut.map(_.amount).sum
459-
if (remoteClosingFee > lastCommitFeeSatoshi) {
463+
if (remoteClosingFee > lastCommitFeeSatoshi && !commitments.channelVersion.hasAnchorOutputs) {
460464
log.error(s"remote proposed a commit fee higher than the last commitment fee: remoteClosingFeeSatoshi=${remoteClosingFee.toLong} lastCommitFeeSatoshi=$lastCommitFeeSatoshi")
461465
Left(InvalidCloseFee(commitments.channelId, remoteClosingFee))
462466
} else {

eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import akka.testkit.TestProbe
2121
import fr.acinq.bitcoin.{ByteVector32, ByteVector64, SatoshiLong}
2222
import fr.acinq.eclair.TestConstants.Bob
2323
import fr.acinq.eclair.blockchain._
24-
import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw}
24+
import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeratePerKw, FeeratesPerKw}
2525
import fr.acinq.eclair.channel.Helpers.Closing
2626
import fr.acinq.eclair.channel._
2727
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
28+
import fr.acinq.eclair.transactions.Transactions
2829
import fr.acinq.eclair.wire.{ClosingSigned, Error, Shutdown}
2930
import fr.acinq.eclair.{CltvExpiry, MilliSatoshiLong, TestConstants, TestKitBaseClass}
3031
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
@@ -67,9 +68,6 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
6768
if (test.tags.contains("fee2")) {
6869
alice.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(4316 sat)))
6970
bob.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(4316 sat)))
70-
} else if (test.tags.contains("anchor_outputs")) {
71-
alice.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(1250 sat)))
72-
bob.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(1250 sat)))
7371
} else {
7472
alice.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(5000 sat)))
7573
bob.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(5000 sat)))
@@ -108,6 +106,7 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
108106
awaitCond(alice.stateData.asInstanceOf[DATA_NEGOTIATING].closingTxProposed.last.map(_.localClosingSigned) == initialState.closingTxProposed.last.map(_.localClosingSigned) :+ aliceCloseSig2)
109107
val Some(closingTx) = alice.stateData.asInstanceOf[DATA_NEGOTIATING].bestUnpublishedClosingTx_opt
110108
assert(closingTx.txOut.length === 2) // NB: in the anchor outputs case, anchors are removed from the closing tx
109+
assert(aliceCloseSig2.feeSatoshis > Transactions.weight2fee(FeeEstimator.AnchorOutputMaxCommitFeerate, closingTx.weight())) // NB: closing fee is allowed to be higher than commit tx fee when using anchor outputs
111110
}
112111

113112
test("recv ClosingSigned (theirCloseFee != ourCloseFee)") {

eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -612,9 +612,6 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
612612

613613
test("recv BITCOIN_TX_CONFIRMED (remote commit, anchor outputs)", Tag("anchor_outputs")) { f =>
614614
import f._
615-
// Set feerates below the 10 sat/byte anchor outputs threshold to ensure a fee negotiation round-trip takes place.
616-
alice.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(1200 sat)))
617-
bob.feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(1200 sat)))
618615
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
619616
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
620617
assert(initialState.commitments.channelVersion === ChannelVersion.ANCHOR_OUTPUTS)

0 commit comments

Comments
 (0)