diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index de1da24a43..362e5806ed 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -1180,9 +1180,14 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Right(signedClosingTx) => // if we are fundee and we were waiting for them to send their first closing_signed, we don't have a lastLocalClosingFee, so we compute a firstClosingFee val lastLocalClosingFee = d.closingTxProposed.last.lastOption.map(_.localClosingSigned.feeSatoshis) - val nextClosingFee = Closing.nextClosingFee( - localClosingFee = lastLocalClosingFee.getOrElse(Closing.firstClosingFee(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)), - remoteClosingFee = remoteClosingFee) + val nextClosingFee = if (d.commitments.localCommit.spec.toLocal == 0.msat) { + // if we have nothing at stake there is no need to negotiate and we accept their fee right away + remoteClosingFee + } else { + Closing.nextClosingFee( + localClosingFee = lastLocalClosingFee.getOrElse(Closing.firstClosingFee(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)), + remoteClosingFee = remoteClosingFee) + } val (closingTx, closingSigned) = Closing.makeClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nextClosingFee) if (lastLocalClosingFee.contains(nextClosingFee)) { // next computed fee is the same than the one we previously sent (probably because of rounding), let's close now diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index 51a1beca33..4d50c6f09f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -137,6 +137,22 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike testFeeConverge(f) } + test("recv ClosingSigned (nothing at stake)", Tag("no_push_msat")) { f => + import f._ + val aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis + alice2bob.forward(bob) + val bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis + assert(aliceCloseFee === bobCloseFee) + bob2alice.forward(alice) + val mutualCloseTxAlice = alice2blockchain.expectMsgType[PublishAsap].tx + val mutualCloseTxBob = bob2blockchain.expectMsgType[PublishAsap].tx + assert(mutualCloseTxAlice === mutualCloseTxBob) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(mutualCloseTxAlice)) + assert(bob2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(mutualCloseTxBob)) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].mutualClosePublished == List(mutualCloseTxAlice)) + assert(bob.stateData.asInstanceOf[DATA_CLOSING].mutualClosePublished == List(mutualCloseTxBob)) + } + test("recv ClosingSigned (fee too high)") { f => import f._ val aliceCloseSig = alice2bob.expectMsgType[ClosingSigned]