channeldb+lnwallet: store updates the peer should sign under new key

This fixes a long-standing force close bug. When we receive a
revocation, store the updates that the remote should sign next under
a new database key. Previously, these were not persisted which would
lead to force closure.
This commit is contained in:
nsa committed 2020-07-29 19:03:44 -04:00
1 parent a39c91fbcb
commit e4188ba9c2
4 files changed
+326 -9

No files matched your search

+91 -2
View File
@@ -79,6 +79,11 @@ var (
// for in one of our remote commits.
unsignedAckedUpdatesKey = []byte("unsigned-acked-updates-key")
// remoteUnsignedLocalUpdatesKey is an entry in the channel bucket that
// contains the local updates that the remote party has acked, but
// has not yet signed for in one of their local commits.
remoteUnsignedLocalUpdatesKey = []byte("remote-unsigned-local-updates-key")
// revocationStateKey stores their current revocation hash, our
// preimage producer and their preimage store.
revocationStateKey = []byte("revocation-state-key")
@@ -1448,6 +1453,39 @@ func (c *OpenChannel) UpdateCommitment(newCommitment *ChannelCommitment,
"updates: %v", err)
}
// Persist the remote unsigned local updates that are not included
// in our new commitment.
updateBytes := chanBucket.Get(remoteUnsignedLocalUpdatesKey)
if updateBytes == nil {
return nil
}
r := bytes.NewReader(updateBytes)
updates, err := deserializeLogUpdates(r)
if err != nil {
return err
}
var validUpdates []LogUpdate
for _, upd := range updates {
// Filter for updates that are not on our local
// commitment.
if upd.LogIndex >= newCommitment.LocalLogIndex {
validUpdates = append(validUpdates, upd)
}
}
var b2 bytes.Buffer
err = serializeLogUpdates(&b2, validUpdates)
if err != nil {
return fmt.Errorf("unable to serialize log updates: %v", err)
}
err = chanBucket.Put(remoteUnsignedLocalUpdatesKey, b2.Bytes())
if err != nil {
return fmt.Errorf("unable to restore chanbucket: %v", err)
}
return nil
})
if err != nil {
@@ -2065,6 +2103,39 @@ func (c *OpenChannel) UnsignedAckedUpdates() ([]LogUpdate, error) {
return updates, nil
}
// RemoteUnsignedLocalUpdates retrieves the persisted, unsigned local log
// updates that the remote still needs to sign for.
func (c *OpenChannel) RemoteUnsignedLocalUpdates() ([]LogUpdate, error) {
var updates []LogUpdate
err := kvdb.View(c.Db, func(tx kvdb.RTx) error {
chanBucket, err := fetchChanBucket(
tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
)
switch err {
case nil:
break
case ErrNoChanDBExists, ErrNoActiveChannels, ErrChannelNotFound:
return nil
default:
return err
}
updateBytes := chanBucket.Get(remoteUnsignedLocalUpdatesKey)
if updateBytes == nil {
return nil
}
r := bytes.NewReader(updateBytes)
updates, err = deserializeLogUpdates(r)
return err
})
if err != nil {
return nil, err
}
return updates, nil
}
// InsertNextRevocation inserts the _next_ commitment point (revocation) into
// the database, and also modifies the internal RemoteNextRevocation attribute
// to point to the passed key. This method is to be using during final channel
@@ -2101,8 +2172,12 @@ func (c *OpenChannel) InsertNextRevocation(revKey *btcec.PublicKey) error {
// this log can be consulted in order to reconstruct the state needed to
// rectify the situation. This method will add the current commitment for the
// remote party to the revocation log, and promote the current pending
// commitment to the current remote commitment.
func (c *OpenChannel) AdvanceCommitChainTail(fwdPkg *FwdPkg) error {
// commitment to the current remote commitment. The updates parameter is the
// set of local updates that the peer still needs to send us a signature for.
// We store this set of updates in case we go down.
func (c *OpenChannel) AdvanceCommitChainTail(fwdPkg *FwdPkg,
updates []LogUpdate) error {
c.Lock()
defer c.Unlock()
@@ -2226,6 +2301,20 @@ func (c *OpenChannel) AdvanceCommitChainTail(fwdPkg *FwdPkg) error {
return fmt.Errorf("unable to store under unsignedAckedUpdatesKey: %v", err)
}
// Persist the local updates the peer hasn't yet signed so they
// can be restored after restart.
var b2 bytes.Buffer
err = serializeLogUpdates(&b2, updates)
if err != nil {
return err
}
err = chanBucket.Put(remoteUnsignedLocalUpdatesKey, b2.Bytes())
if err != nil {
return fmt.Errorf("unable to restore remote unsigned "+
"local updates: %v", err)
}
newRemoteCommit = &newCommit.Commitment
return nil
+2 -2
View File
@@ -797,7 +797,7 @@ func TestChannelStateTransition(t *testing.T) {
fwdPkg := NewFwdPkg(channel.ShortChanID(), oldRemoteCommit.CommitHeight,
diskCommitDiff.LogUpdates, nil)
err = channel.AdvanceCommitChainTail(fwdPkg)
err = channel.AdvanceCommitChainTail(fwdPkg, nil)
if err != nil {
t.Fatalf("unable to append to revocation log: %v", err)
}
@@ -845,7 +845,7 @@ func TestChannelStateTransition(t *testing.T) {
fwdPkg = NewFwdPkg(channel.ShortChanID(), oldRemoteCommit.CommitHeight, nil, nil)
err = channel.AdvanceCommitChainTail(fwdPkg)
err = channel.AdvanceCommitChainTail(fwdPkg, nil)
if err != nil {
t.Fatalf("unable to append to revocation log: %v", err)
}
+1 -1
View File
@@ -398,7 +398,7 @@ func TestRestoreChannelShells(t *testing.T) {
if err != ErrNoRestoredChannelMutation {
t.Fatalf("able to mutate restored channel")
}
err = channel.AdvanceCommitChainTail(nil)
err = channel.AdvanceCommitChainTail(nil, nil)
if err != ErrNoRestoredChannelMutation {
t.Fatalf("able to mutate restored channel")
}