diff --git a/channeldb/channel.go b/channeldb/channel.go index 7cdfb73e..4810b5e6 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -51,6 +51,10 @@ var ( // preimage producer and their preimage store. revocationStateKey = []byte("revocation-state-key") + // dataLossCommitPointKey stores the commitment point received from the + // remote peer during a channel sync in case we have lost channel state. + dataLossCommitPointKey = []byte("data-loss-commit-point-key") + // commitDiffKey stores the current pending commitment state we've // extended to the remote party (if any). Each time we propose a new // state, we store the information necessary to reconstruct this state @@ -97,6 +101,10 @@ var ( // decoded because the byte slice is of an invalid length. ErrInvalidCircuitKeyLen = fmt.Errorf( "length of serialized circuit key must be 16 bytes") + + // ErrNoCommitPoint is returned when no data loss commit point is found + // in the database. + ErrNoCommitPoint = fmt.Errorf("no commit point found") ) // ChannelType is an enum-like type that describes one of several possible @@ -285,8 +293,8 @@ type ChannelCommitment struct { // * lets just walk through } -// ChannelStatus is used to indicate whether an OpenChannel is in the default -// usable state, or a state where it shouldn't be used. +// ChannelStatus is a bit vector used to indicate whether an OpenChannel is in +// the default usable state, or a state where it shouldn't be used. type ChannelStatus uint8 var ( @@ -300,7 +308,14 @@ var ( // CommitmentBroadcasted indicates that a commitment for this channel // has been broadcasted. - CommitmentBroadcasted ChannelStatus = 2 + CommitmentBroadcasted ChannelStatus = 1 << 1 + + // LocalDataLoss indicates that we have lost channel state for this + // channel, and broadcasting our latest commitment might be considered + // a breach. + // TODO(halseh): actually enforce that we are not force closing such a + // channel. + LocalDataLoss ChannelStatus = 1 << 2 ) // String returns a human-readable representation of the ChannelStatus. @@ -312,8 +327,10 @@ func (c ChannelStatus) String() string { return "Borked" case CommitmentBroadcasted: return "CommitmentBroadcasted" + case LocalDataLoss: + return "LocalDataLoss" default: - return "Unknown" + return fmt.Sprintf("Unknown(%08b)", c) } } @@ -679,6 +696,85 @@ func (c *OpenChannel) MarkAsOpen(openLoc lnwire.ShortChannelID) error { return nil } +// MarkDataLoss marks sets the channel status to LocalDataLoss and stores the +// passed commitPoint for use to retrieve funds in case the remote force closes +// the channel. +func (c *OpenChannel) MarkDataLoss(commitPoint *btcec.PublicKey) error { + c.Lock() + defer c.Unlock() + + var status ChannelStatus + if err := c.Db.Update(func(tx *bolt.Tx) error { + chanBucket, err := updateChanBucket( + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, + ) + if err != nil { + return err + } + + channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint) + if err != nil { + return err + } + + // Add status LocalDataLoss to the existing bitvector found in + // the DB. + status = channel.chanStatus | LocalDataLoss + channel.chanStatus = status + + var b bytes.Buffer + if err := WriteElement(&b, commitPoint); err != nil { + return err + } + + err = chanBucket.Put(dataLossCommitPointKey, b.Bytes()) + if err != nil { + return err + } + + return putOpenChannel(chanBucket, channel) + }); err != nil { + return err + } + + // Update the in-memory representation to keep it in sync with the DB. + c.chanStatus = status + + return nil +} + +// DataLossCommitPoint retrieves the stored commit point set during +// MarkDataLoss. If not found ErrNoCommitPoint is returned. +func (c *OpenChannel) DataLossCommitPoint() (*btcec.PublicKey, error) { + var commitPoint *btcec.PublicKey + + err := c.Db.View(func(tx *bolt.Tx) error { + chanBucket, err := readChanBucket(tx, c.IdentityPub, + &c.FundingOutpoint, c.ChainHash) + if err == ErrNoActiveChannels || err == ErrNoChanDBExists { + return ErrNoCommitPoint + } else if err != nil { + return err + } + + bs := chanBucket.Get(dataLossCommitPointKey) + if bs == nil { + return ErrNoCommitPoint + } + r := bytes.NewReader(bs) + if err := ReadElements(r, &commitPoint); err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + return commitPoint, nil +} + // MarkBorked marks the event when the channel as reached an irreconcilable // state, such as a channel breach or state desynchronization. Borked channels // should never be added to the switch. @@ -713,6 +809,8 @@ func (c *OpenChannel) putChanStatus(status ChannelStatus) error { return err } + // Add this status to the existing bitvector found in the DB. + status = channel.chanStatus | status channel.chanStatus = status return putOpenChannel(chanBucket, channel)