1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-22 22:25:26 +01:00

Add more splice RBF reconnection tests (#2964)

We add more tests around disconnection in the middle of signing an RBF
attempt, and verify more details of the `channel_reestablish` message
sent on reconnection.
This commit is contained in:
Bastien Teinturier 2024-12-12 17:49:39 +01:00 committed by GitHub
parent e28f23fbc8
commit 61af10ac71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 431 additions and 99 deletions

View file

@ -47,6 +47,8 @@ import scala.concurrent.Await
import scala.concurrent.duration._
object ChannelStateTestsTags {
/** If set, the channel funding transaction will have more than 6 confirmations. */
val FundingDeeplyBuried = "funding_deeply_buried"
/** If set, channels will not use option_support_large_channel. */
val DisableWumbo = "disable_wumbo"
/** If set, channels will use option_dual_fund. */
@ -362,6 +364,11 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
}
alice2blockchain.expectMsgType[WatchFundingDeeplyBuried]
bob2blockchain.expectMsgType[WatchFundingDeeplyBuried]
if (tags.contains(ChannelStateTestsTags.FundingDeeplyBuried)) {
val fundingTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localFundingStatus.signedTx_opt.get
alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, fundingTx)
bob ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, fundingTx)
}
eventually(assert(alice.stateName == NORMAL))
eventually(assert(bob.stateName == NORMAL))
@ -566,7 +573,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
// we watch the confirmation of the "final" transactions that send funds to our wallets (main delayed output and 2nd stage htlc transactions)
assert(s2blockchain.expectMsgType[WatchTxConfirmed].txId == commitTx.txid)
localCommitPublished.claimMainDelayedOutputTx.foreach(claimMain => {
val watchConfirmed = s2blockchain.expectMsgType[WatchTxConfirmed]
val watchConfirmed = s2blockchain.expectMsgType[WatchTxConfirmed]
assert(watchConfirmed.txId == claimMain.tx.txid)
assert(watchConfirmed.delay_opt.map(_.parentTxId).contains(publishedLocalCommitTx.txid))
})

View file

@ -365,8 +365,8 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
import f._
val fundingTxId = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED].signingSession.fundingTx.txId
alice2bob.expectMsgType[CommitSig]
bob2alice.expectMsgType[CommitSig]
alice2bob.expectMsgType[CommitSig] // Bob doesn't receive Alice's commit_sig
bob2alice.expectMsgType[CommitSig] // Alice doesn't receive Bob's commit_sig
awaitCond(alice.stateName == WAIT_FOR_DUAL_FUNDING_SIGNED)
awaitCond(bob.stateName == WAIT_FOR_DUAL_FUNDING_SIGNED)
@ -378,6 +378,25 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
reconnect(f, fundingTxId)
}
test("recv INPUT_DISCONNECTED (commit_sig partially received)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
import f._
val fundingTxId = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_SIGNED].signingSession.fundingTx.txId
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[CommitSig] // Alice doesn't receive Bob's commit_sig
bob2alice.expectMsgType[TxSignatures] // Alice doesn't receive Bob's tx_signatures
awaitCond(alice.stateName == WAIT_FOR_DUAL_FUNDING_SIGNED)
awaitCond(bob.stateName == WAIT_FOR_DUAL_FUNDING_CONFIRMED)
alice ! INPUT_DISCONNECTED
awaitCond(alice.stateName == OFFLINE)
bob ! INPUT_DISCONNECTED
awaitCond(bob.stateName == OFFLINE)
reconnect(f, fundingTxId)
}
test("recv INPUT_DISCONNECTED (commit_sig received)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
import f._
@ -445,9 +464,13 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
val bobInit = Init(bob.underlyingActor.nodeParams.features.initFeatures())
alice ! INPUT_RECONNECTED(bob, aliceInit, bobInit)
bob ! INPUT_RECONNECTED(alice, bobInit, aliceInit)
assert(alice2bob.expectMsgType[ChannelReestablish].nextFundingTxId_opt.contains(fundingTxId))
val channelReestablishAlice = alice2bob.expectMsgType[ChannelReestablish]
assert(channelReestablishAlice.nextFundingTxId_opt.contains(fundingTxId))
assert(channelReestablishAlice.nextLocalCommitmentNumber == 1)
alice2bob.forward(bob)
assert(bob2alice.expectMsgType[ChannelReestablish].nextFundingTxId_opt.contains(fundingTxId))
val channelReestablishBob = bob2alice.expectMsgType[ChannelReestablish]
assert(channelReestablishBob.nextFundingTxId_opt.contains(fundingTxId))
assert(channelReestablishBob.nextLocalCommitmentNumber == 1)
bob2alice.forward(alice)
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)

View file

@ -876,7 +876,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
assert(alice.stateName == WAIT_FOR_DUAL_FUNDING_CONFIRMED)
}
test("recv INPUT_DISCONNECTED (unsigned rbf attempt)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
private def initiateRbf(f: FixtureParam): Unit = {
import f._
alice ! CMD_BUMP_FUNDING_FEE(TestProbe().ref, TestConstants.feeratePerKw * 1.1, fundingFeeBudget = 100_000.sat, 0, None)
@ -900,28 +900,43 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
alice2bob.forward(bob)
bob2alice.expectMsgType[TxComplete]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxComplete] // bob doesn't receive alice's tx_complete
alice2bob.expectMsgType[CommitSig] // bob doesn't receive alice's commit_sig
}
awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs])
val rbfTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.asInstanceOf[DualFundingStatus.RbfWaitingForSigs].signingSession.fundingTx
assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.isInstanceOf[DualFundingStatus.RbfInProgress])
private def reconnectRbf(f: FixtureParam): (ChannelReestablish, ChannelReestablish) = {
import f._
alice ! INPUT_DISCONNECTED
awaitCond(alice.stateName == OFFLINE)
assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs])
bob ! INPUT_DISCONNECTED
awaitCond(bob.stateName == OFFLINE)
assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations)
val aliceInit = Init(alice.underlyingActor.nodeParams.features.initFeatures())
val bobInit = Init(bob.underlyingActor.nodeParams.features.initFeatures())
alice ! INPUT_RECONNECTED(bob, aliceInit, bobInit)
bob ! INPUT_RECONNECTED(alice, bobInit, aliceInit)
assert(alice2bob.expectMsgType[ChannelReestablish].nextFundingTxId_opt.contains(rbfTx.txId))
val channelReestablishAlice = alice2bob.expectMsgType[ChannelReestablish]
alice2bob.forward(bob)
assert(bob2alice.expectMsgType[ChannelReestablish].nextFundingTxId_opt.isEmpty)
val channelReestablishBob = bob2alice.expectMsgType[ChannelReestablish]
bob2alice.forward(alice)
(channelReestablishAlice, channelReestablishBob)
}
test("recv INPUT_DISCONNECTED (unsigned rbf attempt)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
import f._
initiateRbf(f)
alice2bob.expectMsgType[TxComplete] // bob doesn't receive alice's tx_complete
alice2bob.expectMsgType[CommitSig] // bob doesn't receive alice's commit_sig
awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs])
val rbfTxId = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.asInstanceOf[DualFundingStatus.RbfWaitingForSigs].signingSession.fundingTx.txId
assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.isInstanceOf[DualFundingStatus.RbfInProgress])
val (channelReestablishAlice, channelReestablishBob) = reconnectRbf(f)
assert(channelReestablishAlice.nextFundingTxId_opt.contains(rbfTxId))
assert(channelReestablishAlice.nextLocalCommitmentNumber == 1)
assert(channelReestablishBob.nextFundingTxId_opt.isEmpty)
assert(channelReestablishBob.nextLocalCommitmentNumber == 1)
// Bob detects that Alice stored an old RBF attempt and tells her to abort.
bob2alice.expectMsgType[TxAbort]
@ -934,55 +949,25 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
bob2alice.expectNoMessage(100 millis)
}
test("recv INPUT_DISCONNECTED (signed rbf attempt)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
test("recv INPUT_DISCONNECTED (rbf commit_sig partially received)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
import f._
val currentFundingTxId = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.txId
alice ! CMD_BUMP_FUNDING_FEE(TestProbe().ref, TestConstants.feeratePerKw * 1.1, fundingFeeBudget = 100_000.sat, 0, None)
alice2bob.expectMsgType[TxInitRbf]
alice2bob.forward(bob)
bob2alice.expectMsgType[TxAckRbf]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxAddInput]
alice2bob.forward(bob)
bob2alice.expectMsgType[TxAddInput]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxAddInput]
alice2bob.forward(bob)
bob2alice.expectMsgType[TxAddInput]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxAddOutput]
alice2bob.forward(bob)
bob2alice.expectMsgType[TxAddOutput]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxAddOutput]
alice2bob.forward(bob)
bob2alice.expectMsgType[TxComplete]
bob2alice.forward(alice)
initiateRbf(f)
alice2bob.expectMsgType[TxComplete]
alice2bob.forward(bob)
bob2alice.expectMsgType[CommitSig] // alice doesn't receive bob's commit_sig
alice2bob.expectMsgType[CommitSig] // bob doesn't receive alice's commit_sig
awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs])
awaitCond(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs])
val rbfTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.asInstanceOf[DualFundingStatus.RbfWaitingForSigs].signingSession.fundingTx
alice ! INPUT_DISCONNECTED
awaitCond(alice.stateName == OFFLINE)
assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs])
bob ! INPUT_DISCONNECTED
awaitCond(bob.stateName == OFFLINE)
assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs])
val aliceInit = Init(alice.underlyingActor.nodeParams.features.initFeatures())
val bobInit = Init(bob.underlyingActor.nodeParams.features.initFeatures())
alice ! INPUT_RECONNECTED(bob, aliceInit, bobInit)
bob ! INPUT_RECONNECTED(alice, bobInit, aliceInit)
assert(alice2bob.expectMsgType[ChannelReestablish].nextFundingTxId_opt.contains(rbfTx.txId))
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
assert(bob2alice.expectMsgType[ChannelReestablish].nextFundingTxId_opt.contains(rbfTx.txId))
bob2alice.forward(alice)
bob2alice.expectMsgType[CommitSig] // Alice doesn't receive Bob's commit_sig
bob2alice.expectMsgType[TxSignatures] // Alice doesn't receive Bob's tx_signatures
awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs])
awaitCond(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations)
val rbfTxId = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.asInstanceOf[DualFundingStatus.RbfWaitingForSigs].signingSession.fundingTx.txId
val (channelReestablishAlice, channelReestablishBob) = reconnectRbf(f)
assert(channelReestablishAlice.nextFundingTxId_opt.contains(rbfTxId))
assert(channelReestablishAlice.nextLocalCommitmentNumber == 1)
assert(channelReestablishBob.nextFundingTxId_opt.contains(rbfTxId))
assert(channelReestablishBob.nextLocalCommitmentNumber == 1)
// Alice and Bob exchange signatures and complete the RBF attempt.
alice2bob.expectMsgType[CommitSig]
@ -998,7 +983,83 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
assert(alice2blockchain.expectMsgType[WatchFundingConfirmed].txId == nextFundingTx.signedTx.txid)
assert(bobListener.expectMsgType[TransactionPublished].tx.txid == nextFundingTx.signedTx.txid)
assert(bob2blockchain.expectMsgType[WatchFundingConfirmed].txId == nextFundingTx.signedTx.txid)
assert(currentFundingTxId != nextFundingTx.txId)
awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations)
awaitCond(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations)
}
test("recv INPUT_DISCONNECTED (rbf commit_sig received)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
import f._
initiateRbf(f)
alice2bob.expectMsgType[TxComplete]
alice2bob.forward(bob)
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
bob2alice.expectMsgType[TxSignatures] // Alice doesn't receive Bob's tx_signatures
awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.isInstanceOf[DualFundingStatus.RbfWaitingForSigs])
awaitCond(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations)
val rbfTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status.asInstanceOf[DualFundingStatus.RbfWaitingForSigs].signingSession.fundingTx
val (channelReestablishAlice, channelReestablishBob) = reconnectRbf(f)
assert(channelReestablishAlice.nextFundingTxId_opt.contains(rbfTx.txId))
assert(channelReestablishAlice.nextLocalCommitmentNumber == 1)
assert(channelReestablishBob.nextFundingTxId_opt.contains(rbfTx.txId))
assert(channelReestablishBob.nextLocalCommitmentNumber == 1)
// Alice and Bob exchange signatures and complete the RBF attempt.
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
bob2alice.expectMsgType[TxSignatures]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxSignatures]
alice2bob.forward(bob)
val nextFundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction]
assert(aliceListener.expectMsgType[TransactionPublished].tx.txid == nextFundingTx.signedTx.txid)
assert(alice2blockchain.expectMsgType[WatchFundingConfirmed].txId == nextFundingTx.signedTx.txid)
assert(bobListener.expectMsgType[TransactionPublished].tx.txid == nextFundingTx.signedTx.txid)
assert(bob2blockchain.expectMsgType[WatchFundingConfirmed].txId == nextFundingTx.signedTx.txid)
awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations)
awaitCond(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations)
}
test("recv INPUT_DISCONNECTED (rbf tx_signatures partially received)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
import f._
val currentFundingTxId = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.txId
initiateRbf(f)
alice2bob.expectMsgType[TxComplete]
alice2bob.forward(bob)
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
bob2alice.expectMsgType[TxSignatures]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxSignatures] // Bob doesn't receive Alice's tx_signatures
awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations)
awaitCond(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations)
val rbfTxId = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.txId
assert(rbfTxId != currentFundingTxId)
val (channelReestablishAlice, channelReestablishBob) = reconnectRbf(f)
assert(channelReestablishAlice.nextFundingTxId_opt.isEmpty)
assert(channelReestablishAlice.nextLocalCommitmentNumber == 1)
assert(channelReestablishBob.nextFundingTxId_opt.contains(rbfTxId))
assert(channelReestablishBob.nextLocalCommitmentNumber == 1)
// Alice and Bob exchange signatures and complete the RBF attempt.
bob2alice.expectNoMessage(100 millis)
alice2bob.expectMsgType[TxSignatures]
alice2bob.forward(bob)
val nextFundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction]
assert(aliceListener.expectMsgType[TransactionPublished].tx.txid == nextFundingTx.signedTx.txid)
assert(alice2blockchain.expectMsgType[WatchFundingConfirmed].txId == nextFundingTx.signedTx.txid)
assert(bobListener.expectMsgType[TransactionPublished].tx.txid == nextFundingTx.signedTx.txid)
assert(bob2blockchain.expectMsgType[WatchFundingConfirmed].txId == nextFundingTx.signedTx.txid)
awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations)
awaitCond(bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].status == DualFundingStatus.WaitingForConfirmations)
}

View file

@ -60,7 +60,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
implicit val log: akka.event.LoggingAdapter = akka.event.NoLogging
override def withFixture(test: OneArgTest): Outcome = {
val tags = test.tags + ChannelStateTestsTags.DualFunding
val tags = test.tags + ChannelStateTestsTags.DualFunding + ChannelStateTestsTags.FundingDeeplyBuried
val setup = init(tags = tags)
import setup._
reachNormal(setup, tags)
@ -265,19 +265,27 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
private def setupHtlcs(f: FixtureParam): TestHtlcs = {
import f._
// add htlcs in both directions
val htlcsAliceToBob = Seq(
addHtlc(15_000_000 msat, alice, bob, alice2bob, bob2alice),
addHtlc(15_000_000 msat, alice, bob, alice2bob, bob2alice)
)
crossSign(alice, bob, alice2bob, bob2alice)
val htlcsBobToAlice = Seq(
addHtlc(20_000_000 msat, bob, alice, bob2alice, alice2bob),
addHtlc(15_000_000 msat, bob, alice, bob2alice, alice2bob)
)
crossSign(bob, alice, bob2alice, alice2bob)
// Concurrently add htlcs in both directions so that commit indices don't match.
val adda1 = addHtlc(15_000_000 msat, alice, bob, alice2bob, bob2alice)
val adda2 = addHtlc(15_000_000 msat, alice, bob, alice2bob, bob2alice)
alice ! CMD_SIGN()
alice2bob.expectMsgType[CommitSig]
val addb1 = addHtlc(20_000_000 msat, bob, alice, bob2alice, alice2bob)
val addb2 = addHtlc(15_000_000 msat, bob, alice, bob2alice, alice2bob)
alice2bob.forward(bob)
bob2alice.expectMsgType[RevokeAndAck]
bob2alice.forward(alice)
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
alice2bob.expectMsgType[RevokeAndAck]
alice2bob.forward(bob)
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[RevokeAndAck]
bob2alice.forward(alice)
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
assert(initialState.commitments.localCommitIndex != initialState.commitments.remoteCommitIndex)
assert(initialState.commitments.latest.capacity == 1_500_000.sat)
assert(initialState.commitments.latest.localCommit.spec.toLocal == 770_000_000.msat)
assert(initialState.commitments.latest.localCommit.spec.toRemote == 665_000_000.msat)
@ -287,7 +295,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
bob2relayer.expectMsgType[Relayer.RelayForward]
bob2relayer.expectMsgType[Relayer.RelayForward]
TestHtlcs(htlcsAliceToBob, htlcsBobToAlice)
TestHtlcs(Seq(adda1, adda2), Seq(addb1, addb2))
}
def spliceOutFee(f: FixtureParam, capacity: Satoshi): Satoshi = {
@ -317,7 +325,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
assert(postSpliceState.commitments.latest.localCommit.spec.htlcs.collect(outgoing).toSeq.map(_.amountMsat).sum == outgoingHtlcs)
}
def resolveHtlcs(f: FixtureParam, htlcs: TestHtlcs, spliceOutFee: Satoshi): Unit = {
def resolveHtlcs(f: FixtureParam, htlcs: TestHtlcs, spliceOutFee: Satoshi = 0.sat): Unit = {
import f._
checkPostSpliceState(f, spliceOutFee)
@ -729,7 +737,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
test("recv CMD_SPLICE (splice-in + splice-out)") { f =>
val htlcs = setupHtlcs(f)
initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), spliceOut_opt = Some(SpliceOut(100_000 sat, defaultSpliceOutScriptPubKey)))
resolveHtlcs(f, htlcs, spliceOutFee = 0.sat)
resolveHtlcs(f, htlcs)
}
test("recv CMD_BUMP_FUNDING_FEE (splice-in + splice-out)") { f =>
@ -1383,7 +1391,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
assert(alice2bob.expectMsgType[CommitSig].batchSize == 2)
// Bob disconnects before receiving Alice's commit_sig.
disconnect(f)
reconnect(f, interceptFundingDeeplyBuried = false)
reconnect(f)
alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
assert(alice2bob.expectMsgType[CommitSig].batchSize == 2)
@ -1552,7 +1560,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
awaitCond(bob.stateName == OFFLINE)
}
private def reconnect(f: FixtureParam, interceptFundingDeeplyBuried: Boolean = true): (ChannelReestablish, ChannelReestablish) = {
private def reconnect(f: FixtureParam): (ChannelReestablish, ChannelReestablish) = {
import f._
val aliceInit = Init(alice.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.params.localParams.initFeatures)
@ -1563,12 +1571,6 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice2bob.forward(bob)
val channelReestablishBob = bob2alice.expectMsgType[ChannelReestablish]
bob2alice.forward(alice)
if (interceptFundingDeeplyBuried) {
alice2blockchain.expectMsgType[WatchFundingDeeplyBuried]
bob2blockchain.expectMsgType[WatchFundingDeeplyBuried]
}
(channelReestablishAlice, channelReestablishBob)
}
@ -1594,6 +1596,8 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
import f._
val htlcs = setupHtlcs(f)
val aliceCommitIndex = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
val bobCommitIndex = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
val sender = initiateSpliceWithoutSigs(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), spliceOut_opt = Some(SpliceOut(100_000 sat, defaultSpliceOutScriptPubKey)))
alice2bob.expectMsgType[CommitSig] // Bob doesn't receive Alice's commit_sig
@ -1604,9 +1608,22 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
disconnect(f)
val (channelReestablishAlice, channelReestablishBob) = reconnect(f)
assert(channelReestablishAlice.nextFundingTxId_opt.contains(spliceStatus.signingSession.fundingTx.txId))
assert(channelReestablishAlice.nextLocalCommitmentNumber == aliceCommitIndex + 1)
assert(channelReestablishBob.nextFundingTxId_opt.contains(spliceStatus.signingSession.fundingTx.txId))
assert(channelReestablishBob.nextLocalCommitmentNumber == bobCommitIndex + 1)
val spliceTx = exchangeSpliceSigs(f, sender)
// Alice and Bob retransmit commit_sig and tx_signatures.
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
bob2alice.expectMsgType[TxSignatures]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxSignatures]
alice2bob.forward(bob)
sender.expectMsgType[RES_SPLICE]
val spliceTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localFundingStatus.signedTx_opt.get
alice2blockchain.expectWatchFundingConfirmed(spliceTx.txid)
bob2blockchain.expectWatchFundingConfirmed(spliceTx.txid)
alice ! WatchFundingConfirmedTriggered(BlockHeight(42), 0, spliceTx)
@ -1618,13 +1635,16 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
resolveHtlcs(f, htlcs, 0.sat)
resolveHtlcs(f, htlcs)
}
test("disconnect (commit_sig received by alice)") { f =>
import f._
val htlcs = setupHtlcs(f)
val aliceCommitIndex = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
val bobCommitIndex = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
assert(aliceCommitIndex != bobCommitIndex)
val sender = initiateSpliceWithoutSigs(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), spliceOut_opt = Some(SpliceOut(100_000 sat, defaultSpliceOutScriptPubKey)))
alice2bob.expectMsgType[CommitSig] // Bob doesn't receive Alice's commit_sig
@ -1636,9 +1656,22 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
disconnect(f)
val (channelReestablishAlice, channelReestablishBob) = reconnect(f)
assert(channelReestablishAlice.nextFundingTxId_opt.contains(spliceStatus.signingSession.fundingTx.txId))
assert(channelReestablishAlice.nextLocalCommitmentNumber == aliceCommitIndex + 1)
assert(channelReestablishBob.nextFundingTxId_opt.contains(spliceStatus.signingSession.fundingTx.txId))
assert(channelReestablishBob.nextLocalCommitmentNumber == bobCommitIndex + 1)
val spliceTx = exchangeSpliceSigs(f, sender)
// Alice and Bob retransmit commit_sig and tx_signatures.
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
bob2alice.expectMsgType[TxSignatures]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxSignatures]
alice2bob.forward(bob)
sender.expectMsgType[RES_SPLICE]
val spliceTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localFundingStatus.signedTx_opt.get
alice2blockchain.expectWatchFundingConfirmed(spliceTx.txid)
bob2blockchain.expectWatchFundingConfirmed(spliceTx.txid)
alice ! WatchFundingConfirmedTriggered(BlockHeight(42), 0, spliceTx)
@ -1650,13 +1683,15 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
resolveHtlcs(f, htlcs, spliceOutFee = 0.sat)
resolveHtlcs(f, htlcs)
}
test("disconnect (tx_signatures sent by bob)") { f =>
import f._
val htlcs = setupHtlcs(f)
val aliceCommitIndex = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
val bobCommitIndex = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
val sender = initiateSpliceWithoutSigs(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), spliceOut_opt = Some(SpliceOut(100_000 sat, defaultSpliceOutScriptPubKey)))
alice2bob.expectMsgType[CommitSig]
@ -1667,13 +1702,25 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].spliceStatus == SpliceStatus.NoSplice)
disconnect(f)
val (channelReestablishAlice, channelReestablishBob) = reconnect(f, interceptFundingDeeplyBuried = false)
val (channelReestablishAlice, channelReestablishBob) = reconnect(f)
assert(channelReestablishAlice.nextFundingTxId_opt.contains(spliceTxId))
assert(channelReestablishAlice.nextLocalCommitmentNumber == aliceCommitIndex + 1)
assert(channelReestablishBob.nextFundingTxId_opt.contains(spliceTxId))
alice2blockchain.expectMsgType[WatchFundingDeeplyBuried]
assert(channelReestablishBob.nextLocalCommitmentNumber == bobCommitIndex + 1)
bob2blockchain.expectWatchFundingConfirmed(spliceTxId)
val spliceTx = exchangeSpliceSigs(f, sender)
// Alice and Bob retransmit commit_sig and tx_signatures.
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
bob2alice.expectMsgType[TxSignatures]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxSignatures]
alice2bob.forward(bob)
sender.expectMsgType[RES_SPLICE]
val spliceTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localFundingStatus.signedTx_opt.get
alice2blockchain.expectWatchFundingConfirmed(spliceTx.txid)
alice ! WatchFundingConfirmedTriggered(BlockHeight(42), 0, spliceTx)
alice2bob.expectMsgType[SpliceLocked]
@ -1684,13 +1731,15 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
resolveHtlcs(f, htlcs, spliceOutFee = 0.sat)
resolveHtlcs(f, htlcs)
}
test("disconnect (tx_signatures received by alice)") { f =>
import f._
val htlcs = setupHtlcs(f)
val aliceCommitIndex = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
val bobCommitIndex = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
initiateSpliceWithoutSigs(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), spliceOut_opt = Some(SpliceOut(100_000 sat, defaultSpliceOutScriptPubKey)))
alice2bob.expectMsgType[CommitSig]
@ -1704,9 +1753,11 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].spliceStatus == SpliceStatus.NoSplice)
disconnect(f)
val (channelReestablishAlice, channelReestablishBob) = reconnect(f, interceptFundingDeeplyBuried = false)
val (channelReestablishAlice, channelReestablishBob) = reconnect(f)
assert(channelReestablishAlice.nextFundingTxId_opt.isEmpty)
assert(channelReestablishAlice.nextLocalCommitmentNumber == aliceCommitIndex + 1)
assert(channelReestablishBob.nextFundingTxId_opt.contains(spliceTxId))
assert(channelReestablishBob.nextLocalCommitmentNumber == bobCommitIndex + 1)
alice2blockchain.expectWatchFundingConfirmed(spliceTxId)
bob2blockchain.expectWatchFundingConfirmed(spliceTxId)
@ -1714,6 +1765,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 2)
val spliceTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localFundingStatus.signedTx_opt.get
// Alice retransmits tx_signatures.
alice2bob.expectMsgType[TxSignatures]
alice2bob.forward(bob)
alice ! WatchFundingConfirmedTriggered(BlockHeight(42), 0, spliceTx)
@ -1725,13 +1777,15 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
resolveHtlcs(f, htlcs, spliceOutFee = 0.sat)
resolveHtlcs(f, htlcs)
}
test("disconnect (tx_signatures received by alice, zero-conf)", Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val htlcs = setupHtlcs(f)
val aliceCommitIndex = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
val bobCommitIndex = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
initiateSpliceWithoutSigs(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), spliceOut_opt = Some(SpliceOut(100_000 sat, defaultSpliceOutScriptPubKey)))
alice2bob.expectMsgType[CommitSig]
@ -1749,13 +1803,15 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
assert(alice2bob.expectMsgType[SpliceLocked].fundingTxId == spliceTxId) // Bob doesn't receive Alice's splice_locked
disconnect(f)
val (channelReestablishAlice, channelReestablishBob) = reconnect(f, interceptFundingDeeplyBuried = false)
val (channelReestablishAlice, channelReestablishBob) = reconnect(f)
assert(channelReestablishAlice.nextFundingTxId_opt.isEmpty)
assert(channelReestablishAlice.nextLocalCommitmentNumber == aliceCommitIndex + 1)
assert(channelReestablishBob.nextFundingTxId_opt.contains(spliceTxId))
assert(channelReestablishBob.nextLocalCommitmentNumber == bobCommitIndex + 1)
alice2blockchain.expectWatchFundingConfirmed(spliceTxId)
bob2blockchain.expectWatchPublished(spliceTxId)
bob2blockchain.expectMsgType[WatchFundingDeeplyBuried]
// Alice retransmits tx_signatures.
alice2bob.expectMsgType[TxSignatures]
alice2bob.forward(bob)
assert(alice2bob.expectMsgType[SpliceLocked].fundingTxId == spliceTx.txid)
@ -1767,7 +1823,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
resolveHtlcs(f, htlcs, spliceOutFee = 0.sat)
resolveHtlcs(f, htlcs)
}
test("disconnect (tx_signatures sent by alice, splice confirms while bob is offline)") { f =>
@ -1789,7 +1845,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
disconnect(f)
alice ! WatchFundingConfirmedTriggered(BlockHeight(42), 0, spliceTx)
val (channelReestablishAlice, channelReestablishBob) = reconnect(f, interceptFundingDeeplyBuried = false)
val (channelReestablishAlice, channelReestablishBob) = reconnect(f)
assert(channelReestablishAlice.nextFundingTxId_opt.isEmpty)
assert(channelReestablishBob.nextFundingTxId_opt.contains(spliceTx.txid))
bob2alice.expectNoMessage(100 millis)
@ -1802,6 +1858,191 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].spliceStatus == SpliceStatus.NoSplice)
}
test("disconnect (RBF commit_sig not sent)") { f =>
import f._
val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)))
assert(alice2blockchain.expectMsgType[WatchFundingConfirmed].txId == spliceTx.txid)
val sender = TestProbe()
val cmd = CMD_BUMP_FUNDING_FEE(sender.ref, FeeratePerKw(15_000 sat), 50_000 sat, 0, None)
alice ! cmd
exchangeStfu(f)
alice2bob.expectMsgType[TxInitRbf]
alice2bob.forward(bob)
bob2alice.expectMsgType[TxAckRbf]
bob2alice.forward(alice)
alice ! INPUT_DISCONNECTED
sender.expectMsgType[RES_FAILURE[_, _]]
awaitCond(alice.stateName == OFFLINE)
assert(alice.stateData.asInstanceOf[DATA_NORMAL].spliceStatus == SpliceStatus.NoSplice)
}
private def confirmRbfTx(f: FixtureParam): Transaction = {
import f._
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 3)
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 3)
val rbfTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localFundingStatus.signedTx_opt.get
alice2blockchain.expectWatchFundingConfirmed(rbfTx.txid)
bob2blockchain.expectWatchFundingConfirmed(rbfTx.txid)
alice ! WatchFundingConfirmedTriggered(BlockHeight(42), 0, rbfTx)
assert(alice2bob.expectMsgType[SpliceLocked].fundingTxId == rbfTx.txid)
alice2bob.forward(bob)
bob ! WatchFundingConfirmedTriggered(BlockHeight(42), 0, rbfTx)
assert(bob2alice.expectMsgType[SpliceLocked].fundingTxId == rbfTx.txid)
bob2alice.forward(alice)
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fundingTxId == rbfTx.txid)
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fundingTxId == rbfTx.txid)
rbfTx
}
test("disconnect (RBF commit_sig not received)", Tag(ChannelStateTestsTags.FundingDeeplyBuried)) { f =>
import f._
val htlcs = setupHtlcs(f)
val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), spliceOut_opt = Some(SpliceOut(100_000 sat, defaultSpliceOutScriptPubKey)))
assert(alice2blockchain.expectMsgType[WatchFundingConfirmed].txId == spliceTx.txid)
// Alice uses the channel before she tries to RBF.
val (_, add) = addHtlc(25_000_000 msat, alice, bob, alice2bob, bob2alice)
crossSign(alice, bob, alice2bob, bob2alice)
failHtlc(add.id, bob, alice, bob2alice, alice2bob)
crossSign(bob, alice, bob2alice, alice2bob)
val aliceCommitIndex = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
val bobCommitIndex = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
val probe = initiateRbfWithoutSigs(f, FeeratePerKw(15_000 sat), sInputsCount = 2, sOutputsCount = 2)
alice2bob.expectMsgType[CommitSig] // Bob doesn't receive Alice's commit_sig
bob2alice.expectMsgType[CommitSig] // Alice doesn't receive Bob's commit_sig
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].spliceStatus.isInstanceOf[SpliceStatus.SpliceWaitingForSigs])
val rbfTxId = alice.stateData.asInstanceOf[DATA_NORMAL].spliceStatus.asInstanceOf[SpliceStatus.SpliceWaitingForSigs].signingSession.fundingTx.txId
disconnect(f)
val (channelReestablishAlice, channelReestablishBob) = reconnect(f)
assert(channelReestablishAlice.nextFundingTxId_opt.contains(rbfTxId))
assert(channelReestablishAlice.nextLocalCommitmentNumber == aliceCommitIndex + 1)
assert(channelReestablishBob.nextFundingTxId_opt.contains(rbfTxId))
assert(channelReestablishBob.nextLocalCommitmentNumber == bobCommitIndex + 1)
bob2blockchain.expectWatchFundingConfirmed(spliceTx.txid)
// Alice and Bob retransmit commit_sig and tx_signatures.
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
bob2alice.expectMsgType[TxSignatures]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxSignatures]
alice2bob.forward(bob)
probe.expectMsgType[RES_SPLICE]
val rbfTx = confirmRbfTx(f)
assert(rbfTx.txid != spliceTx.txid)
resolveHtlcs(f, htlcs)
}
test("disconnect (RBF commit_sig received by alice)", Tag(ChannelStateTestsTags.FundingDeeplyBuried)) { f =>
import f._
val htlcs = setupHtlcs(f)
val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), spliceOut_opt = Some(SpliceOut(100_000 sat, defaultSpliceOutScriptPubKey)))
assert(alice2blockchain.expectMsgType[WatchFundingConfirmed].txId == spliceTx.txid)
// Bob uses the channel before Alice tries to RBF.
val (_, add) = addHtlc(40_000_000 msat, bob, alice, bob2alice, alice2bob)
crossSign(bob, alice, bob2alice, alice2bob)
failHtlc(add.id, alice, bob, alice2bob, bob2alice)
crossSign(alice, bob, alice2bob, bob2alice)
val aliceCommitIndex = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
val bobCommitIndex = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
val probe = initiateRbfWithoutSigs(f, FeeratePerKw(15_000 sat), sInputsCount = 2, sOutputsCount = 2)
alice2bob.expectMsgType[CommitSig] // Bob doesn't receive Alice's commit_sig
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].spliceStatus.isInstanceOf[SpliceStatus.SpliceWaitingForSigs])
val rbfTxId = alice.stateData.asInstanceOf[DATA_NORMAL].spliceStatus.asInstanceOf[SpliceStatus.SpliceWaitingForSigs].signingSession.fundingTx.txId
disconnect(f)
val (channelReestablishAlice, channelReestablishBob) = reconnect(f)
assert(channelReestablishAlice.nextFundingTxId_opt.contains(rbfTxId))
assert(channelReestablishAlice.nextLocalCommitmentNumber == aliceCommitIndex + 1)
assert(channelReestablishBob.nextFundingTxId_opt.contains(rbfTxId))
assert(channelReestablishBob.nextLocalCommitmentNumber == bobCommitIndex + 1)
bob2blockchain.expectWatchFundingConfirmed(spliceTx.txid)
// Alice and Bob retransmit commit_sig and tx_signatures.
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
bob2alice.expectMsgType[TxSignatures]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxSignatures]
alice2bob.forward(bob)
probe.expectMsgType[RES_SPLICE]
val rbfTx = confirmRbfTx(f)
assert(rbfTx.txid != spliceTx.txid)
resolveHtlcs(f, htlcs)
}
test("disconnect (RBF tx_signatures received by alice)", Tag(ChannelStateTestsTags.FundingDeeplyBuried)) { f =>
import f._
val htlcs = setupHtlcs(f)
val spliceTx = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)), spliceOut_opt = Some(SpliceOut(100_000 sat, defaultSpliceOutScriptPubKey)))
assert(alice2blockchain.expectMsgType[WatchFundingConfirmed].txId == spliceTx.txid)
// Alice and Bob use the channel before Alice tries to RBF.
val (_, addA) = addHtlc(20_000_000 msat, alice, bob, alice2bob, bob2alice)
val (_, addB) = addHtlc(30_000_000 msat, bob, alice, bob2alice, alice2bob)
crossSign(alice, bob, alice2bob, bob2alice)
failHtlc(addA.id, bob, alice, bob2alice, alice2bob)
failHtlc(addB.id, alice, bob, alice2bob, bob2alice)
crossSign(bob, alice, bob2alice, alice2bob)
val aliceCommitIndex = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
val bobCommitIndex = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex
val probe = initiateRbfWithoutSigs(f, FeeratePerKw(15_000 sat), sInputsCount = 2, sOutputsCount = 2)
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
bob2alice.expectMsgType[TxSignatures]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxSignatures] // Bob doesn't receive Alice's tx_signatures.
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].spliceStatus == SpliceStatus.NoSplice)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].spliceStatus == SpliceStatus.NoSplice)
val rbfTxId = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fundingTxId
disconnect(f)
val (channelReestablishAlice, channelReestablishBob) = reconnect(f)
assert(channelReestablishAlice.nextFundingTxId_opt.isEmpty)
assert(channelReestablishAlice.nextLocalCommitmentNumber == aliceCommitIndex + 1)
assert(channelReestablishBob.nextFundingTxId_opt.contains(rbfTxId))
assert(channelReestablishBob.nextLocalCommitmentNumber == bobCommitIndex + 1)
bob2blockchain.expectWatchFundingConfirmed(spliceTx.txid)
// Alice retransmits tx_signatures.
alice2bob.expectMsgType[TxSignatures]
alice2bob.forward(bob)
probe.expectMsgType[RES_SPLICE]
val rbfTx = confirmRbfTx(f)
assert(rbfTx.txid != spliceTx.txid)
resolveHtlcs(f, htlcs)
}
test("don't resend splice_locked when zero-conf channel confirms", Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
@ -2499,7 +2740,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
Transaction.correctlySpends(commitTx, Map(c.commitInput.outPoint -> c.commitInput.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
}
resolveHtlcs(f, htlcs, spliceOutFee = 0.sat)
resolveHtlcs(f, htlcs)
}
test("recv CMD_SPLICE (splice-in + splice-out) with pending htlcs, resolved after splice locked", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
@ -2518,7 +2759,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
resolveHtlcs(f, htlcs, spliceOutFee = 0.sat)
resolveHtlcs(f, htlcs)
}
test("recv multiple CMD_SPLICE (splice-in, splice-out)") { f =>