mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-22 14:22:39 +01:00
Fixes for quiescence back ported from lightning-kmp (#2779)
Fixes some issues we found while porting quiescence to lightning-kmp.
This commit is contained in:
parent
61f1e1f82e
commit
a9b590365c
2 changed files with 53 additions and 39 deletions
|
@ -878,6 +878,11 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
|
|||
stay()
|
||||
}
|
||||
|
||||
case Event(_: Stfu, d: DATA_NORMAL) if d.localShutdown.isDefined =>
|
||||
log.warning("our peer sent stfu but we sent shutdown first")
|
||||
// We don't need to do anything, they should accept our shutdown.
|
||||
stay()
|
||||
|
||||
case Event(msg: Stfu, d: DATA_NORMAL) =>
|
||||
if (d.commitments.params.useQuiescence) {
|
||||
if (d.commitments.remoteIsQuiescent) {
|
||||
|
@ -928,6 +933,10 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
|
|||
|
||||
case Event(_: QuiescenceTimeout, d: DATA_NORMAL) => handleQuiescenceTimeout(d)
|
||||
|
||||
case Event(_: SpliceInit, d: DATA_NORMAL) if d.spliceStatus == SpliceStatus.NoSplice && d.commitments.params.useQuiescence =>
|
||||
log.info("rejecting splice attempt: quiescence not negotiated")
|
||||
stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, InvalidSpliceNotQuiescent(d.channelId).getMessage)
|
||||
|
||||
case Event(msg: SpliceInit, d: DATA_NORMAL) =>
|
||||
d.spliceStatus match {
|
||||
case SpliceStatus.NoSplice | SpliceStatus.NonInitiatorQuiescent =>
|
||||
|
@ -2705,7 +2714,9 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
|
|||
spliceInAmount = cmd.additionalLocalFunding,
|
||||
spliceOut = cmd.spliceOutputs,
|
||||
targetFeerate = targetFeerate)
|
||||
val commitTxFees = Transactions.commitTxTotalCost(d.commitments.params.remoteParams.dustLimit, parentCommitment.remoteCommit.spec, d.commitments.params.commitmentFormat)
|
||||
val commitTxFees = if (d.commitments.params.localParams.isInitiator) {
|
||||
Transactions.commitTxTotalCost(d.commitments.params.remoteParams.dustLimit, parentCommitment.remoteCommit.spec, d.commitments.params.commitmentFormat)
|
||||
} else 0.sat
|
||||
if (fundingContribution < 0.sat && parentCommitment.localCommit.spec.toLocal + fundingContribution < parentCommitment.localChannelReserve(d.commitments.params).max(commitTxFees)) {
|
||||
log.warning(s"cannot do splice: insufficient funds (commitTxFees=$commitTxFees reserve=${parentCommitment.localChannelReserve(d.commitments.params)})")
|
||||
Left(InvalidSpliceRequest(d.channelId))
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package fr.acinq.eclair.channel.states.e
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import akka.actor.typed.scaladsl.adapter.actorRefAdapter
|
||||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.bitcoin.scalacompat.{SatoshiLong, Script}
|
||||
|
@ -115,7 +116,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
test("send stfu after pending local changes have been added") { f =>
|
||||
import f._
|
||||
// we have an unsigned htlc in our local changes
|
||||
addHtlc(10_000 msat, alice, bob, alice2bob, bob2alice)
|
||||
addHtlc(50_000_000 msat, alice, bob, alice2bob, bob2alice)
|
||||
alice ! CMD_SPLICE(TestProbe().ref, spliceIn_opt = Some(SpliceIn(50_000 sat)), spliceOut_opt = None)
|
||||
alice2bob.expectNoMessage(100 millis)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
|
@ -127,7 +128,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
import f._
|
||||
initiateQuiescence(f, sendInitialStfu = false)
|
||||
// we're holding the stfu from alice so that bob can add a pending local change
|
||||
addHtlc(10_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
addHtlc(50_000_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
// bob will not reply to alice's stfu until bob has no pending local commitment changes
|
||||
alice2bob.forward(bob)
|
||||
bob2alice.expectNoMessage(100 millis)
|
||||
|
@ -187,8 +188,8 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
private def receiveSettlementCommand(f: FixtureParam, c: SettlementCommandEnum, sendInitialStfu: Boolean, resetConnection: Boolean = false): Unit = {
|
||||
import f._
|
||||
|
||||
val (preimage, add) = addHtlc(10_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
val cmd = c match {
|
||||
val (preimage, add) = addHtlc(50_000_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
val cmd = c match {
|
||||
case FulfillHtlc => CMD_FULFILL_HTLC(add.id, preimage)
|
||||
case FailHtlc => CMD_FAIL_HTLC(add.id, Left(randomBytes32()))
|
||||
}
|
||||
|
@ -269,7 +270,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
test("recv second stfu while non-initiator waiting for local commitment to be signed") { f =>
|
||||
import f._
|
||||
initiateQuiescence(f, sendInitialStfu = false)
|
||||
val (_, _) = addHtlc(10_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
val (_, _) = addHtlc(50_000_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
alice2bob.forward(bob)
|
||||
// second stfu to bob is ignored
|
||||
bob ! Stfu(channelId(bob), initiator = true)
|
||||
|
@ -278,12 +279,20 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
|
||||
test("recv Shutdown message before initiator receives stfu from remote") { f =>
|
||||
import f._
|
||||
// Alice initiates quiescence.
|
||||
initiateQuiescence(f, sendInitialStfu = false)
|
||||
val bobData = bob.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val forbiddenMsg = Shutdown(channelId(bob), bob.underlyingActor.getOrGenerateFinalScriptPubKey(bobData))
|
||||
bob2alice.forward(alice, forbiddenMsg)
|
||||
// handle Shutdown normally
|
||||
alice2bob.expectMsgType[Shutdown]
|
||||
val stfuAlice = Stfu(channelId(alice), initiator = true)
|
||||
// But Bob is concurrently initiating a mutual close, which should "win".
|
||||
bob ! CMD_CLOSE(ActorRef.noSender, None, None)
|
||||
val shutdownBob = bob2alice.expectMsgType[Shutdown]
|
||||
bob ! stfuAlice
|
||||
bob2alice.expectNoMessage(100 millis)
|
||||
alice ! shutdownBob
|
||||
val shutdownAlice = alice2bob.expectMsgType[Shutdown]
|
||||
awaitCond(alice.stateName == NEGOTIATING)
|
||||
alice2bob.expectMsgType[ClosingSigned]
|
||||
bob ! shutdownAlice
|
||||
awaitCond(bob.stateName == NEGOTIATING)
|
||||
}
|
||||
|
||||
test("recv (forbidden) Shutdown message while quiescent") { f =>
|
||||
|
@ -300,7 +309,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
|
||||
test("recv (forbidden) UpdateFulfillHtlc message while quiescent") { f =>
|
||||
import f._
|
||||
val (preimage, add) = addHtlc(10_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
val (preimage, add) = addHtlc(50_000_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
crossSign(bob, alice, bob2alice, alice2bob)
|
||||
alice2relayer.expectMsg(RelayForward(add))
|
||||
initiateQuiescence(f, sendInitialStfu = true)
|
||||
|
@ -315,7 +324,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
|
||||
test("recv (forbidden) UpdateFailHtlc message while quiescent") { f =>
|
||||
import f._
|
||||
val (_, add) = addHtlc(10_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
val (_, add) = addHtlc(50_000_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
crossSign(bob, alice, bob2alice, alice2bob)
|
||||
initiateQuiescence(f, sendInitialStfu = true)
|
||||
val forbiddenMsg = UpdateFailHtlc(channelId(bob), add.id, randomBytes32())
|
||||
|
@ -328,7 +337,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
|
||||
test("recv (forbidden) UpdateFee message while quiescent") { f =>
|
||||
import f._
|
||||
val (_, _) = addHtlc(10_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
val (_, _) = addHtlc(50_000_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
crossSign(bob, alice, bob2alice, alice2bob)
|
||||
initiateQuiescence(f, sendInitialStfu = true)
|
||||
val forbiddenMsg = UpdateFee(channelId(bob), FeeratePerKw(500 sat))
|
||||
|
@ -353,7 +362,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
|
||||
test("recv stfu from splice initiator that is not quiescent") { f =>
|
||||
import f._
|
||||
addHtlc(10_000 msat, alice, bob, alice2bob, bob2alice)
|
||||
addHtlc(50_000_000 msat, alice, bob, alice2bob, bob2alice)
|
||||
alice2bob.forward(bob, Stfu(channelId(alice), initiator = true))
|
||||
bob2alice.expectMsg(Warning(channelId(bob), InvalidSpliceNotQuiescent(channelId(bob)).getMessage))
|
||||
// we should disconnect after giving alice time to receive the warning
|
||||
|
@ -365,7 +374,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
|
||||
test("recv stfu from splice non-initiator that is not quiescent") { f =>
|
||||
import f._
|
||||
addHtlc(10_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
addHtlc(50_000_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
initiateQuiescence(f, sendInitialStfu = false)
|
||||
alice2bob.forward(bob)
|
||||
bob2alice.forward(alice, Stfu(channelId(bob), initiator = false))
|
||||
|
@ -393,10 +402,10 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
sender.expectMsgType[RES_FAILURE[CMD_SPLICE, ConcurrentRemoteSplice]]
|
||||
}
|
||||
|
||||
test("initiate quiescence concurrently (pending changes on initiator side)") { f =>
|
||||
test("initiate quiescence concurrently (pending changes on one side)") { f =>
|
||||
import f._
|
||||
|
||||
addHtlc(10_000 msat, alice, bob, alice2bob, bob2alice)
|
||||
addHtlc(50_000_000 msat, alice, bob, alice2bob, bob2alice)
|
||||
val sender = TestProbe()
|
||||
val cmd = CMD_SPLICE(sender.ref, spliceIn_opt = Some(SpliceIn(500_000 sat, pushAmount = 0 msat)), spliceOut_opt = None)
|
||||
alice ! cmd
|
||||
|
@ -412,26 +421,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
bob2alice.expectMsgType[SpliceInit]
|
||||
}
|
||||
|
||||
test("initiate quiescence concurrently (pending changes on non-initiator side)") { f =>
|
||||
import f._
|
||||
|
||||
addHtlc(10_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
val sender = TestProbe()
|
||||
val cmd = CMD_SPLICE(sender.ref, spliceIn_opt = Some(SpliceIn(500_000 sat, pushAmount = 0 msat)), spliceOut_opt = None)
|
||||
alice ! cmd
|
||||
alice2bob.expectMsgType[Stfu]
|
||||
bob ! cmd
|
||||
bob2alice.expectNoMessage(100 millis) // bob isn't quiescent yet
|
||||
alice2bob.forward(bob)
|
||||
crossSign(bob, alice, bob2alice, alice2bob)
|
||||
bob2alice.expectMsgType[Stfu]
|
||||
bob2alice.forward(alice)
|
||||
assert(bob.stateData.asInstanceOf[DATA_NORMAL].spliceStatus == SpliceStatus.NonInitiatorQuiescent)
|
||||
sender.expectMsgType[RES_FAILURE[CMD_SPLICE, ConcurrentRemoteSplice]]
|
||||
alice2bob.expectMsgType[SpliceInit]
|
||||
}
|
||||
|
||||
test("htlc timeout during quiescence negotiation") { f =>
|
||||
test("outgoing htlc timeout during quiescence negotiation") { f =>
|
||||
import f._
|
||||
val (_, add) = addHtlc(50_000_000 msat, alice, bob, alice2bob, bob2alice)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
|
@ -455,7 +445,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
channelUpdateListener.expectMsgType[LocalChannelDown]
|
||||
}
|
||||
|
||||
test("htlc timeout during quiescence negotiation (with pending preimage)") { f =>
|
||||
test("incoming htlc timeout during quiescence negotiation") { f =>
|
||||
import f._
|
||||
val (preimage, add) = addHtlc(50_000_000 msat, alice, bob, alice2bob, bob2alice)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
|
@ -466,6 +456,10 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
assert(bobCommit.htlcTxsAndRemoteSigs.size == 1)
|
||||
val htlcSuccessTx = bobCommit.htlcTxsAndRemoteSigs.head.htlcTx.tx
|
||||
|
||||
// bob does not force-close unless there is a pending preimage for the incoming htlc
|
||||
bob ! CurrentBlockHeight(add.cltvExpiry.blockHeight - Bob.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt)
|
||||
bob2blockchain.expectNoMessage(100 millis)
|
||||
|
||||
// bob receives the fulfill for htlc, which is ignored because the channel is quiescent
|
||||
val fulfillHtlc = CMD_FULFILL_HTLC(add.id, preimage)
|
||||
safeSend(bob, Seq(fulfillHtlc))
|
||||
|
@ -522,4 +516,13 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
bob2alice.expectMsg(Warning(channelId(bob), SpliceAttemptTimedOut(channelId(bob)).getMessage))
|
||||
}
|
||||
|
||||
test("receive SpliceInit when channel is not quiescent") { f =>
|
||||
import f._
|
||||
val spliceInit = SpliceInit(channelId(alice), 500_000.sat, FeeratePerKw(253.sat), 0, randomKey().publicKey)
|
||||
alice ! spliceInit
|
||||
// quiescence not negotiated
|
||||
alice2bob.expectMsgType[TxAbort]
|
||||
assert(alice.stateData.asInstanceOf[DATA_NORMAL].spliceStatus == SpliceStatus.SpliceAborted)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue