diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 27ea6ac93..85ccc3056 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -9521,3 +9521,125 @@ func TestChannelLocalUnsignedUpdatesFailure(t *testing.T) { err = newAliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) require.NoError(t, err) } + +// TestChannelSignedAckRegression tests a previously-regressing state +// transition no longer causes channel desynchronization. +// +// The full state transition of this test is: +// +// Alice Bob +// <----add------- +// <----sig------- +// -----rev------> +// -----sig------> +// <----rev------- +// ----settle----> +// -----sig------> +// <----rev------- +// <----sig------- +// -----add------> +// -----sig------> +// <----rev------- +// *restarts* +// -----rev------> +// <----sig------- +// +func TestChannelSignedAckRegression(t *testing.T) { + t.Parallel() + + // Create test channels to test out this state transition. + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels( + channeldb.SingleFunderTweaklessBit, + ) + require.NoError(t, err) + defer cleanUp() + + // Create an HTLC that Bob will send to Alice. + htlc, preimage := createHTLC(0, lnwire.MilliSatoshi(5000000)) + + // <----add------ + _, err = bobChannel.AddHTLC(htlc, nil) + require.NoError(t, err) + _, err = aliceChannel.ReceiveHTLC(htlc) + require.NoError(t, err) + + // Force a state transition to lock in the HTLC. + // <----sig------ + // -----rev-----> + // -----sig-----> + // <----rev------ + err = ForceStateTransition(bobChannel, aliceChannel) + require.NoError(t, err) + + // Alice settles the HTLC back to Bob. + // ----settle---> + err = aliceChannel.SettleHTLC(preimage, uint64(0), nil, nil, nil) + require.NoError(t, err) + err = bobChannel.ReceiveHTLCSettle(preimage, uint64(0)) + require.NoError(t, err) + + // -----sig----> + aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + require.NoError(t, err) + err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + require.NoError(t, err) + + // <----rev----- + bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + require.NoError(t, err) + _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + require.NoError(t, err) + + // <----sig----- + bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + require.NoError(t, err) + err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + require.NoError(t, err) + + // Create an HTLC that Alice will send to Bob. + htlc2, _ := createHTLC(0, lnwire.MilliSatoshi(5000000)) + + // -----add----> + _, err = aliceChannel.AddHTLC(htlc2, nil) + require.NoError(t, err) + _, err = bobChannel.ReceiveHTLC(htlc2) + require.NoError(t, err) + + // -----sig----> + aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + require.NoError(t, err) + err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + require.NoError(t, err) + + // <----rev----- + bobRevocation, _, err = bobChannel.RevokeCurrentCommitment() + require.NoError(t, err) + _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) + require.NoError(t, err) + + // Restart Bob's channel state here. + newBobChannel, err := NewLightningChannel( + bobChannel.Signer, bobChannel.channelState, + bobChannel.sigPool, + ) + require.NoError(t, err) + + // -----rev----> + aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + require.NoError(t, err) + fwdPkg, _, _, _, err := newBobChannel.ReceiveRevocation(aliceRevocation) + require.NoError(t, err) + + // Assert that the fwdpkg is not empty. + require.Equal(t, len(fwdPkg.SettleFails), 1) + + // Bob should no longer fail to sign this commitment due to faulty + // update logs. + // <----sig----- + bobSig, bobHtlcSigs, _, err = newBobChannel.SignNextCommitment() + require.NoError(t, err) + + // Alice should receive the new commitment without hiccups. + err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + require.NoError(t, err) +}