From 95f6261d358c66fe62516daa282b64b857bcefaa Mon Sep 17 00:00:00 2001 From: t-bast Date: Fri, 13 Dec 2024 15:50:43 +0100 Subject: [PATCH] Correctly set `next_commitment_number` during splice reconnect As pointed out in https://github.com/lightning/bolts/pull/1214, when reconnecting a partially signed `interactive-tx` session, we should set `next_commitment_number` to the current commitment number if we haven't received our peer's `commit_sig`, which tells them they need to retransmit it. More importantly, if our peer behaves correctly and sends us the current commitment number, we must not think that they're late and halt, waiting for them to send `error`. This commit fixes that by allowing our peers to use the current commitment number when they set `next_funding_txid`. Note that we keep retransmitting our `commit_sig` regardless of the value our peer set in `next_commitment_number`, because we need to wait for them to have an opportunity to upgrade. In a future commit we will stop sending spurious `commit_sig`. --- .../acinq/lightning/channel/InteractiveTx.kt | 5 ++ .../acinq/lightning/channel/states/Channel.kt | 25 ++++-- .../acinq/lightning/channel/states/Syncing.kt | 10 ++- .../channel/states/SpliceTestsCommon.kt | 89 ++++++++++++++----- .../channel/states/SyncingTestsCommon.kt | 87 +++++++++++++++++- 5 files changed, 184 insertions(+), 32 deletions(-) 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()