diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt index c8ff55668..ebef13b46 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/InteractiveTx.kt @@ -1028,6 +1028,11 @@ data class InteractiveTxSigningSession( is Either.Left -> localCommit.value.commitTx.input is Either.Right -> localCommit.value.publishableTxs.commitTx.input } + // This value tells our peer whether we need them to retransmit their commit_sig on reconnection or not. + val reconnectNextLocalCommitmentNumber = when (localCommit) { + is Either.Left -> localCommit.value.index + is Either.Right -> localCommit.value.index + 1 + } fun receiveCommitSig(channelKeys: KeyManager.ChannelKeys, channelParams: ChannelParams, remoteCommitSig: CommitSig, currentBlockHeight: Long, logger: MDCLogger): Pair { return when (localCommit) { diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Channel.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Channel.kt index 705edeea1..3ffdc5263 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Channel.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Channel.kt @@ -16,10 +16,12 @@ import fr.acinq.lightning.channel.Helpers.Closing.claimRevokedRemoteCommitTxOutp import fr.acinq.lightning.channel.Helpers.Closing.getRemotePerCommitmentSecret import fr.acinq.lightning.crypto.KeyManager import fr.acinq.lightning.db.ChannelClosingType -import fr.acinq.lightning.logging.* +import fr.acinq.lightning.logging.LoggingContext +import fr.acinq.lightning.logging.MDCLogger import fr.acinq.lightning.serialization.channel.Encryption.from import fr.acinq.lightning.transactions.Transactions.TransactionWithInputInfo.ClosingTx -import fr.acinq.lightning.utils.* +import fr.acinq.lightning.utils.msat +import fr.acinq.lightning.utils.sat import fr.acinq.lightning.wire.* import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull @@ -306,7 +308,7 @@ sealed class PersistedChannelState : ChannelState() { val myFirstPerCommitmentPoint = keyManager.channelKeys(state.channelParams.localParams.fundingKeyPath).commitmentPoint(0) ChannelReestablish( channelId = channelId, - nextLocalCommitmentNumber = 1, + nextLocalCommitmentNumber = state.signingSession.reconnectNextLocalCommitmentNumber, nextRemoteRevocationNumber = 0, yourLastCommitmentSecret = PrivateKey(ByteVector32.Zeroes), myCurrentPerCommitmentPoint = myFirstPerCommitmentPoint, @@ -316,15 +318,28 @@ sealed class PersistedChannelState : ChannelState() { is ChannelStateWithCommitments -> { val yourLastPerCommitmentSecret = state.commitments.remotePerCommitmentSecrets.lastIndex?.let { state.commitments.remotePerCommitmentSecrets.getHash(it) } ?: ByteVector32.Zeroes val myCurrentPerCommitmentPoint = keyManager.channelKeys(state.commitments.params.localParams.fundingKeyPath).commitmentPoint(state.commitments.localCommitIndex) + // If we disconnected while signing a funding transaction, we may need our peer to retransmit their commit_sig. + val nextLocalCommitmentNumber = when (state) { + is WaitForFundingConfirmed -> when (state.rbfStatus) { + is RbfStatus.WaitingForSigs -> state.rbfStatus.session.reconnectNextLocalCommitmentNumber + else -> state.commitments.localCommitIndex + 1 + } + is Normal -> when (state.spliceStatus) { + is SpliceStatus.WaitingForSigs -> state.spliceStatus.session.reconnectNextLocalCommitmentNumber + else -> state.commitments.localCommitIndex + 1 + } + else -> state.commitments.localCommitIndex + 1 + } + // If we disconnected while signing a funding transaction, we may need our peer to (re)transmit their tx_signatures. val unsignedFundingTxId = when (state) { is WaitForFundingConfirmed -> state.getUnsignedFundingTxId() - is Normal -> state.getUnsignedFundingTxId() // a splice was in progress, we tell our peer that we are remembering it and are expecting signatures + is Normal -> state.getUnsignedFundingTxId() else -> null } val tlvs: TlvStream = unsignedFundingTxId?.let { TlvStream(ChannelReestablishTlv.NextFunding(it)) } ?: TlvStream.empty() ChannelReestablish( channelId = channelId, - nextLocalCommitmentNumber = state.commitments.localCommitIndex + 1, + nextLocalCommitmentNumber = nextLocalCommitmentNumber, nextRemoteRevocationNumber = state.commitments.remoteCommitIndex, yourLastCommitmentSecret = PrivateKey(yourLastPerCommitmentSecret), myCurrentPerCommitmentPoint = myCurrentPerCommitmentPoint, diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Syncing.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Syncing.kt index a14f9e471..645a53396 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Syncing.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Syncing.kt @@ -406,7 +406,7 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: // we resend the same updates and the same sig, and preserve the same ordering val signedUpdates = commitments.changes.localChanges.signed val commitSigs = commitments.active.map { it.nextRemoteCommit }.filterIsInstance().map { it.sig } - SyncResult.Success(retransmit = when (retransmitRevocation) { + val retransmit = when (retransmitRevocation) { null -> buildList { addAll(signedUpdates) addAll(commitSigs) @@ -424,7 +424,8 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: addAll(commitSigs) } } - }) + } + SyncResult.Success(retransmit) } remoteChannelReestablish.nextLocalCommitmentNumber == (commitments.nextRemoteCommitIndex + 1) -> { // we just sent a new commit_sig, they have received it but we haven't received their revocation @@ -449,6 +450,11 @@ data class Syncing(val state: PersistedChannelState, val channelReestablishSent: // they have acknowledged the last commit_sig we sent SyncResult.Success(retransmit = listOfNotNull(retransmitRevocation)) } + remoteChannelReestablish.nextLocalCommitmentNumber == commitments.remoteCommitIndex && remoteChannelReestablish.nextFundingTxId != null -> { + // they haven't received the commit_sig we sent as part of signing a splice transaction + // we will retransmit it before exchanging tx_signatures + SyncResult.Success(retransmit = listOfNotNull(retransmitRevocation)) + } remoteChannelReestablish.nextLocalCommitmentNumber < (commitments.remoteCommitIndex + 1) -> { // they are behind SyncResult.Failure.RemoteLate diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt index 9c8422ba1..fe3bbe8e9 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt @@ -711,13 +711,15 @@ class SpliceTestsCommon : LightningTestSuite() { fun `disconnect -- commit_sig not received`() { val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, htlcs) = setupHtlcs(alice, bob) + val aliceCommitIndex = alice0.commitments.localCommitIndex + val bobCommitIndex = bob0.commitments.localCommitIndex val (alice1, _, bob1, _) = spliceInAndOutWithoutSigs(alice0, bob0, inAmounts = listOf(50_000.sat), outAmount = 100_000.sat) - val spliceStatus = alice1.state.spliceStatus assertIs(spliceStatus) val (alice2, bob2, channelReestablishAlice) = disconnect(alice1, bob1) assertEquals(channelReestablishAlice.nextFundingTxId, spliceStatus.session.fundingTx.txId) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, aliceCommitIndex) val (bob3, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(channelReestablishAlice)) assertIs>(bob3) assertEquals(actionsBob3.size, 4) @@ -725,6 +727,7 @@ class SpliceTestsCommon : LightningTestSuite() { val commitSigBob = actionsBob3.findOutgoingMessage() assertEquals(htlcs.aliceToBob.map { it.second }.toSet(), actionsBob3.filterIsInstance().map { it.add }.toSet()) assertEquals(channelReestablishBob.nextFundingTxId, spliceStatus.session.fundingTx.txId) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, bobCommitIndex) val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(channelReestablishBob)) assertIs>(alice3) assertEquals(actionsAlice3.size, 3) @@ -738,35 +741,63 @@ class SpliceTestsCommon : LightningTestSuite() { fun `disconnect -- commit_sig received by alice`() { val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice1, bob1, htlcs) = setupHtlcs(alice, bob) - val (alice2, _, bob2, commitSigBob1) = spliceInAndOutWithoutSigs(alice1, bob1, inAmounts = listOf(50_000.sat), outAmount = 100_000.sat) - val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(commitSigBob1)) + val aliceCommitIndex = alice1.commitments.localCommitIndex + val bobCommitIndex = bob1.commitments.localCommitIndex + val (alice2, _, bob2, commitSigBob) = spliceInAndOutWithoutSigs(alice1, bob1, inAmounts = listOf(50_000.sat), outAmount = 100_000.sat) + val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(commitSigBob)) assertIs>(alice3) assertTrue(actionsAlice3.isEmpty()) val spliceStatus = alice3.state.spliceStatus assertIs(spliceStatus) + val spliceTxId = spliceStatus.session.fundingTx.txId val (alice4, bob3, channelReestablishAlice) = disconnect(alice3, bob2) - assertEquals(channelReestablishAlice.nextFundingTxId, spliceStatus.session.fundingTx.txId) + assertEquals(channelReestablishAlice.nextFundingTxId, spliceTxId) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, aliceCommitIndex + 1) val (bob4, actionsBob4) = bob3.process(ChannelCommand.MessageReceived(channelReestablishAlice)) - assertIs>(bob4) assertEquals(actionsBob4.size, 4) val channelReestablishBob = actionsBob4.findOutgoingMessage() - val commitSigBob2 = actionsBob4.findOutgoingMessage() + actionsBob4.hasOutgoingMessage() assertEquals(htlcs.aliceToBob.map { it.second }.toSet(), actionsBob4.filterIsInstance().map { it.add }.toSet()) - assertEquals(channelReestablishBob.nextFundingTxId, spliceStatus.session.fundingTx.txId) + assertEquals(channelReestablishBob.nextFundingTxId, spliceTxId) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, bobCommitIndex) val (alice5, actionsAlice5) = alice4.process(ChannelCommand.MessageReceived(channelReestablishBob)) - assertIs>(alice5) assertEquals(actionsAlice5.size, 3) val commitSigAlice = actionsAlice5.findOutgoingMessage() assertEquals(htlcs.bobToAlice.map { it.second }.toSet(), actionsAlice5.filterIsInstance().map { it.add }.toSet()) - val (alice6, bob5) = exchangeSpliceSigs(alice5, commitSigAlice, bob4, commitSigBob2) - resolveHtlcs(alice6, bob5, htlcs, commitmentsCount = 2) + + val (bob5, actionsBob5) = bob4.process(ChannelCommand.MessageReceived(commitSigAlice)) + assertEquals(actionsBob5.size, 5) + val txSigsBob = actionsBob5.findOutgoingMessage() + actionsBob5.hasWatchConfirmed(txSigsBob.txId) + assertEquals(htlcs.aliceToBob.map { it.second }.toSet(), actionsBob5.filterIsInstance().map { it.add }.toSet()) + actionsBob5.has() + val (alice6, actionsAlice6) = alice5.process(ChannelCommand.MessageReceived(txSigsBob)) + assertIs>(alice6) + assertEquals(actionsAlice6.size, 8) + assertEquals(actionsAlice6.hasPublishTx(ChannelAction.Blockchain.PublishTx.Type.FundingTx).txid, spliceTxId) + actionsAlice6.hasWatchConfirmed(spliceTxId) + assertEquals(htlcs.bobToAlice.map { it.second }.toSet(), actionsAlice6.filterIsInstance().map { it.add }.toSet()) + actionsAlice6.has() + actionsAlice6.has() + actionsAlice6.has() + val txSigsAlice = actionsAlice6.findOutgoingMessage() + + val (bob6, actionsBob6) = bob5.process(ChannelCommand.MessageReceived(txSigsAlice)) + assertIs>(bob6) + assertEquals(actionsBob6.size, 2) + assertEquals(actionsBob6.hasPublishTx(ChannelAction.Blockchain.PublishTx.Type.FundingTx).txid, txSigsBob.txId) + actionsBob6.has() + + resolveHtlcs(alice6, bob6, htlcs, commitmentsCount = 2) } @Test - fun `disconnect -- tx_signatures sent by bob`() { + fun `disconnect -- commit_sig received by bob`() { val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, htlcs) = setupHtlcs(alice, bob) + val aliceCommitIndex = alice0.commitments.localCommitIndex + val bobCommitIndex = bob0.commitments.localCommitIndex val (alice1, commitSigAlice1, bob1, _) = spliceInAndOutWithoutSigs(alice0, bob0, inAmounts = listOf(80_000.sat), outAmount = 50_000.sat) val (bob2, actionsBob2) = bob1.process(ChannelCommand.MessageReceived(commitSigAlice1)) assertIs>(bob2) @@ -775,6 +806,7 @@ class SpliceTestsCommon : LightningTestSuite() { val (alice2, bob3, channelReestablishAlice) = disconnect(alice1, bob2) assertEquals(channelReestablishAlice.nextFundingTxId, spliceTxId) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, aliceCommitIndex) val (bob4, actionsBob4) = bob3.process(ChannelCommand.MessageReceived(channelReestablishAlice)) assertEquals(actionsBob4.size, 5) val channelReestablishBob = actionsBob4.findOutgoingMessage() @@ -782,6 +814,7 @@ class SpliceTestsCommon : LightningTestSuite() { assertEquals(htlcs.aliceToBob.map { it.second }.toSet(), actionsBob4.filterIsInstance().map { it.add }.toSet()) val txSigsBob = actionsBob4.findOutgoingMessage() assertEquals(channelReestablishBob.nextFundingTxId, spliceTxId) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, bobCommitIndex + 1) val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(channelReestablishBob)) assertEquals(actionsAlice3.size, 3) assertEquals(htlcs.bobToAlice.map { it.second }.toSet(), actionsAlice3.filterIsInstance().map { it.add }.toSet()) @@ -794,10 +827,11 @@ class SpliceTestsCommon : LightningTestSuite() { assertEquals(alice5.state.commitments.active.size, 2) assertEquals(actionsAlice5.size, 8) assertEquals(actionsAlice5.hasPublishTx(ChannelAction.Blockchain.PublishTx.Type.FundingTx).txid, spliceTxId) - assertEquals(htlcs.bobToAlice.map { it.second }.toSet(), actionsAlice5.filterIsInstance().map { it.add }.toSet()) actionsAlice5.hasWatchConfirmed(spliceTxId) + assertEquals(htlcs.bobToAlice.map { it.second }.toSet(), actionsAlice5.filterIsInstance().map { it.add }.toSet()) actionsAlice5.has() actionsAlice5.has() + actionsAlice5.has() val txSigsAlice = actionsAlice5.findOutgoingMessage() val (bob5, actionsBob5) = bob4.process(ChannelCommand.MessageReceived(commitSigAlice2)) @@ -808,10 +842,12 @@ class SpliceTestsCommon : LightningTestSuite() { assertEquals(actionsBob6.size, 2) assertEquals(actionsBob6.hasPublishTx(ChannelAction.Blockchain.PublishTx.Type.FundingTx).txid, spliceTxId) actionsBob6.has() + + resolveHtlcs(alice5, bob6, htlcs, commitmentsCount = 2) } @Test - fun `disconnect -- tx_signatures sent by bob -- zero-conf`() { + fun `disconnect -- commit_sig received by bob -- zero-conf`() { val (alice, bob) = reachNormalWithConfirmedFundingTx(zeroConf = true) val (alice0, bob0, htlcs) = setupHtlcs(alice, bob) val (alice1, commitSigAlice1, bob1, _) = spliceInAndOutWithoutSigs(alice0, bob0, inAmounts = listOf(75_000.sat), outAmount = 120_000.sat) @@ -878,7 +914,7 @@ class SpliceTestsCommon : LightningTestSuite() { } @Test - fun `disconnect -- tx_signatures sent by alice -- confirms while bob is offline`() { + fun `disconnect -- tx_signatures received by alice -- confirms while bob is offline`() { val (alice, bob) = reachNormalWithConfirmedFundingTx() val (alice0, bob0, htlcs) = setupHtlcs(alice, bob) val (alice1, commitSigAlice1, bob1, commitSigBob1) = spliceInAndOutWithoutSigs(alice0, bob0, inAmounts = listOf(70_000.sat, 60_000.sat), outAmount = 150_000.sat) @@ -1846,20 +1882,25 @@ class SpliceTestsCommon : LightningTestSuite() { private fun setupHtlcs(alice: LNChannel, bob: LNChannel): Triple, LNChannel, TestHtlcs> { val (nodes1, preimage1, add1) = addHtlc(15_000_000.msat, alice, bob) - val (nodes2, preimage2, add2) = addHtlc(15_000_000.msat, nodes1.first, nodes1.second) - val (alice3, bob3) = crossSign(nodes2.first, nodes2.second) - val (nodes3, preimage3, add3) = addHtlc(20_000_000.msat, bob3, alice3) - val (nodes4, preimage4, add4) = addHtlc(15_000_000.msat, nodes3.first, nodes3.second) - val (bob5, alice5) = crossSign(nodes4.first, nodes4.second) + val (alice1, bob1) = nodes1 + val (nodes2, preimage2, add2) = addHtlc(15_000_000.msat, alice1, bob1) + val (alice2, bob2) = nodes2 + val (nodes3, preimage3, add3) = addHtlc(20_000_000.msat, bob2, alice2) + val (bob3, alice3) = nodes3 + val (alice4, bob4) = crossSign(alice3, bob3) + val (nodes5, preimage4, add4) = addHtlc(15_000_000.msat, bob4, alice4) + val (bob5, alice5) = nodes5 + val (bob6, alice6) = crossSign(bob5, alice5) - assertIs(alice5.state) - assertEquals(1_000_000.sat, alice5.state.commitments.latest.fundingAmount) - assertEquals(820_000_000.msat, alice5.state.commitments.latest.localCommit.spec.toLocal) - assertEquals(115_000_000.msat, alice5.state.commitments.latest.localCommit.spec.toRemote) + assertIs(alice6.state) + assertEquals(1_000_000.sat, alice6.state.commitments.latest.fundingAmount) + assertEquals(820_000_000.msat, alice6.state.commitments.latest.localCommit.spec.toLocal) + assertEquals(115_000_000.msat, alice6.state.commitments.latest.localCommit.spec.toRemote) + assertNotEquals(alice6.state.commitments.localCommitIndex, bob6.state.commitments.localCommitIndex) val aliceToBob = listOf(Pair(preimage1, add1), Pair(preimage2, add2)) val bobToAlice = listOf(Pair(preimage3, add3), Pair(preimage4, add4)) - return Triple(alice5, bob5, TestHtlcs(aliceToBob, bobToAlice)) + return Triple(alice6, bob6, TestHtlcs(aliceToBob, bobToAlice)) } private fun resolveHtlcs(alice: LNChannel, bob: LNChannel, htlcs: TestHtlcs, commitmentsCount: Int): Pair, LNChannel> { diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SyncingTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SyncingTestsCommon.kt index e69527e5e..9d06669c2 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SyncingTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SyncingTestsCommon.kt @@ -93,7 +93,9 @@ class SyncingTestsCommon : LightningTestSuite() { val (alice1, bob1, channelReestablishAlice, channelReestablishBob) = disconnectWithBackup(alice, bob) assertNotNull(channelReestablishBob) assertEquals(channelReestablishAlice.nextFundingTxId, fundingTxId) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 0) assertEquals(channelReestablishBob.nextFundingTxId, fundingTxId) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 0) val (bob2, actionsBob2) = bob1.process(ChannelCommand.MessageReceived(channelReestablishAlice)) assertEquals(actionsBob2.size, 1) @@ -117,6 +119,81 @@ class SyncingTestsCommon : LightningTestSuite() { assertEquals(actionsAlice4.find().tx.txid, fundingTxId) } + @Test + fun `reestablish unsigned channel -- commit_sig received by Alice`() { + val (alice, _, bob, commitSigBob) = WaitForFundingSignedTestsCommon.init() + val (alice1, _) = alice.process(ChannelCommand.MessageReceived(commitSigBob)) + assertIs>(alice1) + assertIs(alice1.state) + val fundingTxId = alice1.state.signingSession.fundingTx.txId + val (alice2, bob1, channelReestablishAlice, channelReestablishBob) = disconnectWithBackup(alice1, bob) + assertNotNull(channelReestablishBob) + assertEquals(channelReestablishAlice.nextFundingTxId, fundingTxId) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 1) + assertEquals(channelReestablishBob.nextFundingTxId, fundingTxId) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 0) + + val (bob2, actionsBob2) = bob1.process(ChannelCommand.MessageReceived(channelReestablishAlice)) + val commitSigBob1 = actionsBob2.hasOutgoingMessage() + assertNull(actionsBob2.findOutgoingMessageOpt()) + + val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(channelReestablishBob)) + assertEquals(actionsAlice3.size, 1) + val commitSigAlice = actionsAlice3.hasOutgoingMessage() + val (alice4, _) = alice3.process(ChannelCommand.MessageReceived(commitSigBob1)) + + val (bob3, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(commitSigAlice)) + assertIs(bob3.state) + val txSigsBob = actionsBob3.findOutgoingMessage() + + val (alice5, actionsAlice5) = alice4.process(ChannelCommand.MessageReceived(txSigsBob)) + assertIs(alice5.state) + val txSigsAlice = actionsAlice5.hasOutgoingMessage() + actionsAlice5.has() + assertEquals(actionsAlice5.find().tx.txid, fundingTxId) + + val (bob4, actionsBob4) = bob3.process(ChannelCommand.MessageReceived(txSigsAlice)) + assertIs(bob4.state) + actionsBob4.has() + assertEquals(actionsBob4.find().tx.txid, fundingTxId) + } + + @Test + fun `reestablish unsigned channel -- commit_sig received by Bob`() { + val (alice, commitSigAlice, bob, _) = WaitForFundingSignedTestsCommon.init() + val (bob1, actionsBob1) = bob.process(ChannelCommand.MessageReceived(commitSigAlice)) + assertIs>(bob1) + actionsBob1.hasOutgoingMessage() + val fundingTxId = bob1.state.latestFundingTx.txId + val (alice1, bob2, channelReestablishAlice, channelReestablishBob0) = disconnectWithBackup(alice, bob1) + assertNull(channelReestablishBob0) + assertEquals(channelReestablishAlice.nextFundingTxId, fundingTxId) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 0) + + val (bob3, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(channelReestablishAlice)) + val channelReestablishBob = actionsBob3.hasOutgoingMessage() + assertEquals(channelReestablishBob.nextFundingTxId, fundingTxId) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 1) + val commitSigBob = actionsBob3.hasOutgoingMessage() + val txSigsBob = actionsBob3.hasOutgoingMessage() + + val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(channelReestablishBob)) + assertEquals(actionsAlice2.size, 1) + actionsAlice2.hasOutgoingMessage() + val (alice3, actionsAlice3) = alice2.process(ChannelCommand.MessageReceived(commitSigBob)) + assertTrue(actionsAlice3.isEmpty()) + val (alice4, actionsAlice4) = alice3.process(ChannelCommand.MessageReceived(txSigsBob)) + assertIs(alice4.state) + val txSigsAlice = actionsAlice4.hasOutgoingMessage() + actionsAlice4.has() + assertEquals(actionsAlice4.find().tx.txid, fundingTxId) + + val (bob4, actionsBob4) = bob3.process(ChannelCommand.MessageReceived(txSigsAlice)) + assertIs(bob4.state) + actionsBob4.has() + assertEquals(actionsBob4.find().tx.txid, fundingTxId) + } + @Test fun `reestablish unsigned channel -- commit_sig received`() { val (alice, commitSigAlice, bob, commitSigBob) = WaitForFundingSignedTestsCommon.init() @@ -130,10 +207,12 @@ class SyncingTestsCommon : LightningTestSuite() { val (alice2, bob2, channelReestablishAlice, channelReestablishBob0) = disconnectWithBackup(alice1, bob1) assertNull(channelReestablishBob0) assertEquals(channelReestablishAlice.nextFundingTxId, fundingTxId) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 1) val (bob3, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(channelReestablishAlice)) val channelReestablishBob = actionsBob3.hasOutgoingMessage() assertEquals(channelReestablishBob.nextFundingTxId, fundingTxId) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 1) actionsBob3.hasOutgoingMessage() val txSigsBob = actionsBob3.hasOutgoingMessage() @@ -156,7 +235,7 @@ class SyncingTestsCommon : LightningTestSuite() { } @Test - fun `reestablish unsigned channel -- tx_signatures received`() { + fun `reestablish unsigned channel -- tx_signatures received by Alice`() { val (alice, commitSigAlice, bob, commitSigBob) = WaitForFundingSignedTestsCommon.init() val (alice1, _) = alice.process(ChannelCommand.MessageReceived(commitSigBob)) assertIs>(alice1) @@ -172,11 +251,13 @@ class SyncingTestsCommon : LightningTestSuite() { val (alice3, bob2, channelReestablishAlice, channelReestablishBob0) = disconnectWithBackup(alice2, bob1) assertNull(channelReestablishBob0) assertNull(channelReestablishAlice.nextFundingTxId) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 1) val (bob3, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(channelReestablishAlice)) assertEquals(actionsBob3.size, 1) val channelReestablishBob = actionsBob3.hasOutgoingMessage() assertEquals(channelReestablishBob.nextFundingTxId, fundingTxId) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 1) val (alice4, actionsAlice4) = alice3.process(ChannelCommand.MessageReceived(channelReestablishBob)) assertIs(alice4.state) @@ -195,11 +276,13 @@ class SyncingTestsCommon : LightningTestSuite() { val (alice1, bob1, channelReestablishAlice, channelReestablishBob0) = disconnectWithBackup(alice, bob) assertNull(channelReestablishBob0) assertEquals(channelReestablishAlice.nextFundingTxId, rbfFundingTxId) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 0) val (bob2, actionsBob2) = bob1.process(ChannelCommand.MessageReceived(channelReestablishAlice)) assertEquals(actionsBob2.size, 2) val channelReestablishBob = actionsBob2.hasOutgoingMessage() assertEquals(channelReestablishBob.nextFundingTxId, rbfFundingTxId) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 0) val commitSigBob = actionsBob2.hasOutgoingMessage() val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(channelReestablishBob)) @@ -233,6 +316,7 @@ class SyncingTestsCommon : LightningTestSuite() { val (alice2, bob2, channelReestablishAlice, channelReestablishBob0) = disconnectWithBackup(alice1, bob1) assertNull(channelReestablishBob0) assertEquals(channelReestablishAlice.nextFundingTxId, rbfFundingTxId) + assertEquals(channelReestablishAlice.nextLocalCommitmentNumber, 1) val (bob3, actionsBob3) = bob2.process(ChannelCommand.MessageReceived(channelReestablishAlice)) assertIs(bob3.state) @@ -240,6 +324,7 @@ class SyncingTestsCommon : LightningTestSuite() { assertEquals(actionsBob3.size, 3) val channelReestablishBob = actionsBob3.hasOutgoingMessage() assertEquals(channelReestablishBob.nextFundingTxId, rbfFundingTxId) + assertEquals(channelReestablishBob.nextLocalCommitmentNumber, 1) actionsBob3.hasOutgoingMessage() val txSigsBob = actionsBob3.hasOutgoingMessage()