diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index f076ae3a..5347e132 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -683,14 +683,37 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) { } } +// TestCooperativeChannelClosure checks that the coop close process finishes +// with an agreement from both parties, and that the final balances of the +// close tx check out. func TestCooperativeChannelClosure(t *testing.T) { + t.Run("tweakless", func(t *testing.T) { + testCoopClose(t, &coopCloseTestCase{ + chanType: channeldb.SingleFunderTweaklessBit, + }) + }) + t.Run("anchors", func(t *testing.T) { + testCoopClose(t, &coopCloseTestCase{ + chanType: channeldb.SingleFunderTweaklessBit | + channeldb.AnchorOutputsBit, + anchorAmt: anchorSize * 2, + }) + }) +} + +type coopCloseTestCase struct { + chanType channeldb.ChannelType + anchorAmt btcutil.Amount +} + +func testCoopClose(t *testing.T, testCase *coopCloseTestCase) { t.Parallel() // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. aliceChannel, bobChannel, cleanUp, err := CreateTestChannels( - channeldb.SingleFunderTweaklessBit, + testCase.chanType, ) if err != nil { t.Fatalf("unable to create test channels: %v", err) @@ -707,7 +730,7 @@ func TestCooperativeChannelClosure(t *testing.T) { bobChannel.channelState.LocalCommitment.FeePerKw, ) - // We'll store with both Alice and Bob creating a new close proposal + // We'll start with both Alice and Bob creating a new close proposal // with the same fee. aliceFee := aliceChannel.CalcFee(aliceFeeRate) aliceSig, _, _, err := aliceChannel.CreateCloseProposal( @@ -728,7 +751,7 @@ func TestCooperativeChannelClosure(t *testing.T) { // With the proposals created, both sides should be able to properly // process the other party's signature. This indicates that the // transaction is well formed, and the signatures verify. - aliceCloseTx, _, err := bobChannel.CompleteCooperativeClose( + aliceCloseTx, bobTxBalance, err := bobChannel.CompleteCooperativeClose( bobSig, aliceSig, bobDeliveryScript, aliceDeliveryScript, bobFee, ) @@ -737,7 +760,7 @@ func TestCooperativeChannelClosure(t *testing.T) { } bobCloseSha := aliceCloseTx.TxHash() - bobCloseTx, _, err := aliceChannel.CompleteCooperativeClose( + bobCloseTx, aliceTxBalance, err := aliceChannel.CompleteCooperativeClose( aliceSig, bobSig, aliceDeliveryScript, bobDeliveryScript, aliceFee, ) @@ -749,6 +772,32 @@ func TestCooperativeChannelClosure(t *testing.T) { if bobCloseSha != aliceCloseSha { t.Fatalf("alice and bob close transactions don't match: %v", err) } + + // Finally, make sure the final balances are correct from both's + // perspective. + aliceBalance := aliceChannel.channelState.LocalCommitment. + LocalBalance.ToSatoshis() + + // The commit balance have had the initiator's (Alice) commitfee and + // any anchors subtracted, so add that back to the final expected + // balance. Alice also pays the coop close fee, so that must be + // subtracted. + commitFee := aliceChannel.channelState.LocalCommitment.CommitFee + expBalanceAlice := aliceBalance + commitFee + + testCase.anchorAmt - bobFee + if aliceTxBalance != expBalanceAlice { + t.Fatalf("expected balance %v got %v", expBalanceAlice, + aliceTxBalance) + } + + // Bob is not the initiator, so his final balance should simply be + // equal to the latest commitment balance. + expBalanceBob := bobChannel.channelState.LocalCommitment. + LocalBalance.ToSatoshis() + if bobTxBalance != expBalanceBob { + t.Fatalf("expected bob's balance to be %v got %v", + expBalanceBob, bobTxBalance) + } } // TestForceClose checks that the resulting ForceCloseSummary is correct when a diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index ed7121d5..1039bbfc 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -675,11 +675,22 @@ func CoopCloseBalance(chanType channeldb.ChannelType, isInitiator bool, // We'll make sure we account for the complete balance by adding the // current dangling commitment fee to the balance of the initiator. - commitFee := localCommit.CommitFee + initiatorDelta := localCommit.CommitFee + + // Since the initiator's balance also is stored after subtracting the + // anchor values, add that back in case this was an anchor commitment. + if chanType.HasAnchors() { + initiatorDelta += 2 * anchorSize + } + + // The initiator will pay the full coop close fee, subtract that value + // from their balance. + initiatorDelta -= coopCloseFee + if isInitiator { - ourBalance = ourBalance - coopCloseFee + commitFee + ourBalance += initiatorDelta } else { - theirBalance = theirBalance - coopCloseFee + commitFee + theirBalance += initiatorDelta } return ourBalance, theirBalance