lnwallet+contractcourt: gracefully handle auto force close post data loss

In this commit, update the start up logic to gracefully handle a
seemingly rare case. In this case, a peer detects local data loss with a
set of active HTLCs. These HTLCs then eventually expire (they may or may
not actually "exist"), causing a force close decision. Before this PR,
this attempt would fail with a fatal error that can impede start up.

To better handle such a scenario, we'll now catch the error when we fail
to force close due to entering the DLP and instead terminate the state
machine at the broadcast state. When a commitment transaction eventually
confirms, we'll play it as normal.

Fixes https://github.com/lightningnetwork/lnd/issues/7984
This commit is contained in:
Olaoluwa Osuntokun committed 2023-09-15 18:29:09 -07:00
1 parent 90effda090
commit de54a603b7
5 files changed
+112 -25

No files matched your search

+10 -5
View File
@@ -116,6 +116,12 @@ var (
// ErrRevLogDataMissing is returned when a certain wanted optional field
// in a revocation log entry is missing.
ErrRevLogDataMissing = errors.New("revocation log data missing")
// ErrForceCloseLocalDataLoss is returned in the case a user (or
// another sub-system) attempts to force close when we've detected that
// we've likely lost data ourselves.
ErrForceCloseLocalDataLoss = errors.New("cannot force close " +
"channel with local data loss")
)
// ErrCommitSyncLocalDataLoss is returned in the case that we receive a valid
@@ -124,7 +130,7 @@ var (
// height. This means we have lost some critical data, and must fail the
// channel and MUST NOT force close it. Instead we should wait for the remote
// to force close it, such that we can attempt to sweep our funds. The
// commitment point needed to sweep the remote's force close is encapsuled.
// commitment point needed to sweep the remote's force close is encapsulated.
type ErrCommitSyncLocalDataLoss struct {
// ChannelPoint is the identifier for the channel that experienced data
// loss.
@@ -7308,8 +7314,6 @@ type LocalForceCloseSummary struct {
// outputs within the commitment transaction.
//
// TODO(roasbeef): all methods need to abort if in dispute state
// TODO(roasbeef): method to generate CloseSummaries for when the remote peer
// does a unilateral close
func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) {
lc.Lock()
defer lc.Unlock()
@@ -7318,8 +7322,9 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) {
// 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())
return nil, fmt.Errorf("%w: channel_state=%v",
ErrForceCloseLocalDataLoss,
lc.channelState.ChanStatus())
}
commitTx, err := lc.getSignedCommitTx()
+1 -4
View File
@@ -7417,10 +7417,7 @@ func TestForceCloseFailLocalDataLoss(t *testing.T) {
// 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")
}
require.ErrorIs(t, err, ErrForceCloseLocalDataLoss)
}
// TestForceCloseBorkedState tests that once we force close a channel, it's