diff --git a/lnwallet/channel.go b/lnwallet/channel.go index f10f20a7..9ed768e1 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -5640,14 +5640,24 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) { lc.Lock() defer lc.Unlock() + // If we've detected local data loss for this channel, then we won't + // allow a force close, as it may be the case that we have a dated + // version of the commitment, or this is actually a channel shell. + if lc.channelState.HasChanStatus(channeldb.ChanStatusLocalDataLoss) { + return nil, fmt.Errorf("cannot force close channel with "+ + "state: %v", lc.channelState.ChanStatus()) + } + commitTx, err := lc.getSignedCommitTx() if err != nil { return nil, err } localCommitment := lc.channelState.LocalCommitment - summary, err := NewLocalForceCloseSummary(lc.channelState, - lc.Signer, lc.pCache, commitTx, localCommitment) + summary, err := NewLocalForceCloseSummary( + lc.channelState, lc.Signer, lc.pCache, commitTx, + localCommitment, + ) if err != nil { return nil, err } diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index e1c88e9e..f9e4821a 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -17,6 +17,7 @@ import ( "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" ) @@ -6274,3 +6275,33 @@ func TestChannelRestoreCommitHeight(t *testing.T) { bobChannel = restoreAndAssertCommitHeights(t, bobChannel, true, 0, 2, 1) bobChannel = restoreAndAssertCommitHeights(t, bobChannel, true, 1, 2, 2) } + +// TestForceCloseFailLocalDataLoss tests that we don't allow a force close of a +// channel that's in a non-default state. +func TestForceCloseFailLocalDataLoss(t *testing.T) { + t.Parallel() + + aliceChannel, _, cleanUp, err := CreateTestChannels() + if err != nil { + t.Fatalf("unable to create test channels: %v", err) + } + defer cleanUp() + + // Now that we have our set of channels, we'll modify the channel state + // to have a non-default channel flag. + err = aliceChannel.channelState.ApplyChanStatus( + channeldb.ChanStatusLocalDataLoss, + ) + if err != nil { + t.Fatalf("unable to apply channel state: %v", err) + } + + // Due to the change above, if we attempt to force close this + // channel, we should fail as it isn't safe to force close a + // channel that isn't in the pure default state. + _, err = aliceChannel.ForceClose() + if err == nil { + t.Fatalf("expected force close to fail due to non-default " + + "chan state") + } +}